Как работает логгер пользователей в майнкрафт
Из-за новой политики Microsoft в отношении сторонних ресурсов, Minecraft Wiki больше не является официальной. В связи с этим были внесены некоторые изменения, в том числе и обновлён логотип вики-проекта. Подробности на нашем Discord-сервере.
Небольшое введение [ ]
В данной статье приведён список всех блоков и предметов, добавляемых модом Security Craft. Каждый перечисленный элемент имеет краткое описание.
Эффект «нескольких логгеров»
с использованием глобального логгера через свойство Logger публичного статического класса Serilog.Log .
Таким образом, есть возможность получить две разные подсистемы логирования в пределах одного приложения просто перепутав входной параметр preserveStaticLogger . И поскольку логи — это то, с помощью чего разбираются с багами, то с таким логированием эту проблему будет сложно найти.
Но всё не так плохо. Параметр preserveStaticLogger по умолчанию false . А это значит, если ничего специально не делать, логгер при обоих способах логирования будет один и будет иметь одну и ту же конфигурацию.
Security Craft/Блоки и предметы
Список блоков и предметов [ ]
Техническое [ ]
Техническое (ориг. Technical) — первый раздел, добавляемый модом Security Craft. На данный момент содержит в себе 52 блоков и предметов. Блоки по большей части предназначены для защиты территорий/личных вещей от несанкционированного доступа к ним других игроков и мобов. Предметы же созданы для улучшения и редактирования свойств этих же и не только блоков.
Блоки [ ]
Для того, чтобы убрать часового, достаточно нажать по нему ? Shift + ПКМ .
Предметы [ ]
Позволяет модифицировать большую часть техническим блоков путём их настройки и/или внедрением модулей.
Взрывчатое [ ]
Взрывчатое (ориг. Explosives) — второй раздел Security Craft. На данный момент содержит в себе 22 блока. Все они считаются опасными по причине того, что могут создавать взрывы, если выполнено необходимое для этого условие. На своих владельцев эти блоки никаким образом не реагируют!
Изображение | Название | Описание |
---|---|---|
Заминированные блоки | Наступив или сломав данные блоки, произойдёт взрыв. | |
Заминированная печь | Взрывается при нажатии ПКМ . | |
Заминированные рельсы | Взрываются, если по ним проезжает вагонетка. | |
Умная Система Боеприпасов | Проверяет территорию вокруг себя в радиусе 12 блоков на наличие игроков и/или мобов. При обнаружении пускает в них разрывные огненные снаряды. Когда у У. С. Б. заканчиваются боеприпасы, его можно разрушить, однако после этого ничего не выпадет. | |
Клеймор | Взрывается через 1 секунду, если сквозь него пройдёт игрок или моб. Если же его сломать, взрыв произойдёт мгновенно. | |
Мина | Взрывается при попытке сломать, нажать ПКМ или пройтись по ней. | |
Подпрыгивающая Бетти | При любом взаимодействии с данной миной она сначала подпрыгнет, а через 0.5 секунды взорвётся. |
Декоративное [ ]
Декоративное (ориг. Decoration) — третий раздел Security Craft. На данный момент содержит в себе 367 блоков. Все блоки из данного раздела являются практически неразрушаемыми аналогами строительных блоков из ванильного Minecraft.
Предисловие
В 100% случаев, когда Я сталкивался с применением Serilog, это был код одного из его примеров.
В этом примере мы видим, как разработчики Serilog предлагают его использовать:
создаём глобальный статический логгер и конфигурируем для вывода в консоль. Это временно, пока не будет применена конфигурация приложения;
сообщаем о запуске приложения через глобальный логгер
запускаем хост приложения;
сообщаем об успешном останове приложения через глобальный логгер;
при необходимости логируем ошибку запуска приложения через глобальный логгер;
закрываем и флашим глобальный логгер;
интегрируем и конфигурируем Serilog, в том числе с помощью конфигурации приложения.
Подразумевается, что в момент запуска приложения, глобальный логгер будет заменён экземпляром, который будет сконфигурирован в соответствии с конфигурацией приложения. Т.е. запись про успешный останов и про необработанную ошибку будут уже выведены не (не только) в консоль.
Проблемы с тестированием
Так как глобальный логгер хранится в статическом свойстве класса, то это единый объект на домен приложения. Инициализируется для коллекции сервисов, поэтому, если в рамках одного домена приложения будет создано несколько коллекций сервисов и инициализирована подсистема логирования, то в глобальном логгере будет конфигурация последнего из них и все логи от всех сервисов этих коллекций будут писаться через него. Такая проблема, например, возникает в модульных тестах.
Проверим это! Для этого понадобится вспомогательные классы.
Тестовый логгер
Отправитель запросов
Вспомогательный класс по отправке запроса тестовому приложению, запущенному прямо в тесте. Нужен для того, чтобы меньше дублировать код в тестах:
В методе Send инициируется приложение с добавлением Serilog и логированием в тестовый логгер.
Тесты
Результаты тестирования
Вывод по тестированию
Так как логгер глобальный и один на все тесты, а тесты выполняются параллельно, то после инициализации веб-приложения в каждом тесте, в качестве глобального логгера остаётся тот логгер, который был присвоен статическому полю Serilog.Log.Logger позже. Поскольку сдвиг по времени в работе тестов непредсказуем, то в разных случаях глобальным становится логгер из разных тестов.
8 продвинутых возможностей модуля logging в Python, которые вы не должны пропустить
Python предоставляет довольно мощный и гибкий встроенный модуль logging со множеством возможностей. В этой статье я хочу поделиться восемью продвинутыми возможностями, которые будут полезны при разработке ПО.
Основы модуля logging
Прежде чем приступить к рассмотрению продвинутых возможностей, давайте убедимся, что у нас есть базовое понимание модуля logging.
Логгер
Форматировщик и обработчик
У любого логгера есть несколько конфигураций, которые могут быть модифицированы. Более продвинутые конфигурации мы обсудим позже, а наиболее ходовые — это форматировщик (прим. пер.: formatter) и обработчик (прим. пер.: handler).
Кастомизированный форматировщик может выглядеть так:
Например:
В этом примере мы создали main.py, package1.py, и app_logger.py. Модуль app_logger.py содержит функцию get_logger, которая возвращает экземпляр логгера. Экземпляр логгера включает в себя кастомный форматировщик и два обработчика: StreamHandler с уровнем INFO и FileHandler с уровнем WARNING. Важно установить базовый уровень в INFO или DEBUG (уровень журналирования по умолчанию — WARNING), в противном случае любые записи журнала по уровню ниже, чем WARNING, будут отфильтрованы. И main.py, и package1.py, используют get_logger, чтобы создавать свои собственные логгеры.
Диаграмма Xiaoxu Gao
Записи с уровнем INFO отправляются как в консольный вывод (sys.stdout), так и в файл журнала, а записи с уровнем WARNING пишутся только в файл журнала. Если вы можете полностью понять, что и почему происходит в этом примере, то мы готовы приступить к более продвинутым возможностям.
1. Создавайте заданные пользователем атрибуты объектов класса LogRecord, используя класс LoggerAdapter
Как я упоминал ранее, у LogRecord есть несколько атрибутов. Разработчики могут выбрать наиболее важные атрибуты и использовать в форматировщике. Помимо того, модуль logging также предоставляет возможность добавить в LogRecord определенные пользователем атрибуты.
Один из способов сделать это — использовать LoggerAdapter. Когда вы создаете адаптер, вы передаете ему экземпляр логгера и свои атрибуты (в словаре). Этот класс предоставляет тот же интерфейс, что и класс Logger, поэтому вы все еще можете вызывать методы наподобие logger.info.
Новый атрибут с фиксированным значением
Новый атрибут с динамическим значением
2. Создавайте определенные пользователем атрибуты объектов класса LogRecord, используя класс Filter
Диаграмма из официальной документации Python
3. Многопоточность с модулем logging
Модуль logging на самом деле реализован потокобезопасным способом, поэтому нам не нужны дополнительные усилия. Код ниже показывает, что MainThread и WorkThread разделяют один и тот же экземпляр логгера без проблемы состояния гонки. Также есть встроенный атрибут threadName для форматировщика.
Под капотом модуль logging использует threading.RLock() практически везде. Отличия между RLock от Lock:
Lock может быть получен только один раз и больше не может быть получен до тех пор, пока он не будет освобожден. С другой стороны, RLock может быть получен неоднократно до своего освобождения, но он должен быть освобожден столько же раз.
Lock может быть освобожден любым потоком, а RLock — только тем потоком, который его удерживает.
Любой обработчик, который наследуется от класса Handler, обладает методом handle(), предназначенным для генерации записей. Ниже представлен блок кода метода Handler.handle(). Как видите, обработчик получит и освободит блокировку до и после генерации записи соответственно. Метод emit() может быть реализован по-разному в разных обработчиках.
4. Многопроцессная обработка с модулем logging — QueueHandler
Несмотря на то, что модуль logging потокобезопасен, он не процессобезопасен. Если вы хотите, чтобы несколько процессов вели запись в один и тот же файл журнала, то вы должны вручную позаботиться о доступе к вашему файлу. В соответствии с учебником по logging, есть несколько вариантов.
QueueHandler + «процесс-потребитель»
Один из вариантов — использование QueueHandler. Идея заключается в том, чтобы создать экземпляр класса multiprocessing.Queue и поделить его между любым количеством процессов. В примере ниже у нас есть 2 «процесса-производителя», которые отправляют записи журнала в очередь и «процесс-потребитель», читающий записи из очереди и пишущий их в файл журнала.
QueueHandler + QueueListener
SocketHandler
Другое решение, предлагаемое учебником, — отправлять записи из нескольких процессов в SocketHandler и иметь отдельный процесс, который реализует сокет-сервер, читающий записи и отправляющий их в место назначения. В этих источниках есть довольно подробная реализация.
Все эти решения, в основном, следуют одному принципу: отправлять записи из разных процессов в централизованное место — либо в очередь, либо на удаленный сервер. Получатель на другой стороне ответственен за их запись в места назначения.
5. По умолчанию не генерируйте какие-либо журнальные записи библиотеки — NullHandler
На данный момент мы упомянули несколько обработчиков, реализованных модулем logging.
Другой полезный встроенный обработчик — NullHandler. В реализации NullHandler практически ничего нет. Тем не менее, он помогает разработчикам отделить библиотечные записи журнала от записей приложения.
Ниже приведена реализация обработчика NullHandler.
Почему нам нужно отделять записи библиотеки от записей приложения?
Сторонняя библиотека, которая использует logging, по умолчанию не должна выбрасывать вывод журналирования, так как он может быть не нужен разработчику/пользователю приложения, которое использует библиотеку.
Наилучшая практика — по умолчанию не генерировать библиотечные записи журнала и давать пользователю библиотеки возможность решить, хочет ли он получать и обрабатывать эти записи в приложении.
В роли разработчика библиотеки нам нужна только одна строка кода внутри init.py, чтобы добавить NullHandler. Во вложенных пакетах и модулях логгеры остаются прежними. Когда мы устанавливаем этот пакет в наше приложение через pip install, мы по умолчанию не увидим библиотечные записи журнала.
Чтобы сделать эти записи видимыми, нужно добавить обработчики в логгер библиотеки в своем приложении.
Если библиотека не использует NullHandler, но вы хотите отключить записи из библиотеки, то можете установить logging.getLogger("package").propagate = False. Если propagate установлен в False, то записи журнала не будут передаваться обработчикам.
6. Делайте ротацию своих файлов журнала — RotatingFileHandler, TimedRotatingFileHandler
Вот этот пример очень похож на пример из учебника. Должно получиться 6 файлов журнала.
Другой обработчик для ротации файлов — TimeRotatingFileHandler, который позволяет разработчикам создавать ротационные журналы, основываясь на истекшем времени. Условия времени включают: секунду, минуту, час, день, день недели (0=Понедельник) и полночь (журнал продлевается в полночь).
В следующем примере мы делаем ротацию файла журнала каждую секунду с пятью резервными файлами. В каждом резервном файле есть временная метка в качестве суффикса.
7. Исключения в процессе журналирования
Зачастую мы используем logger.error() или logger.exception() при обработке исключений. Но что если сам логгер генерирует исключение? Что случится с программой? Ну, зависит от обстоятельств.
Ошибка логгера обрабатывается, когда обработчик вызывает метод emit(). Это означает, что любое исключение, связанное с форматированием или записью, перехватывается обработчиком, а не поднимается. Если конкретнее, метод handleError() будет выводить трассировку в stderr, и программа продолжится. Если у вас есть кастомный обработчик, наследуемый от класса Handler, то вы можете реализовать свой собственный handleError().
Однако, если исключение произошло за пределами emit(), то оно может быть поднято, и программа остановится. Например, в коде ниже мы добавляем дополнительный атрибут id в logger.info без его обработки в LoggerAdapter. Эта ошибка не обработана и приводит к остановке программы.
8. Три разных способа конфигурирования своего логгера
Последний пункт, которым я хотел поделиться, — о конфигурировании своего логгера. Есть три способа конфигурирования логгера.
используйте код
Самый простой вариант — использовать код для конфигурирования своего логгера, так же, как во всех примерах, что мы видели ранее в этой статье. Но недостаток этого варианта в том, что любая модификация (прим. пер.: конфигурации) требует внесения изменений в исходном коде.
use dictConfig
Второй вариант — записывать конфигурацию в словарь и использовать logging.config.dictConfig, чтобы читать ее. Вы также можете сохранить словарь в JSON-файл и читать оттуда. Плюс в том, что этот файл может быть загружен как внешняя конфигурация, но он может способствовать появлению ошибок из-за своей структуры.
используйте fileConfig
И последний, но не менее важный, третий вариант — использовать logging.config.fileConfig Конфигурация записывается в отдельный файл формата .ini.
Есть возможность обновлять конфигурацию во время выполнения программы через сервер конфигурации. Учебник показывает пример как со стороны клиента, так и со стороны сервера. Конфигурация обновляется посредством подключения через сокет, а на стороне клиента мы используем c = logging.config.listen(PORT) c.start(), чтобы получать самую новую конфигурацию.
Надеюсь, эти советы и приемы, связанные с модулем logging, могут помочь вам создать вокруг вашего приложения хороший фреймворк для журналирования без ущерба для производительности. Если у вас есть что-нибудь, чем можно поделиться, пожалуйста, оставьте комментарий ниже!
Глобальный логгер — где нужен?
Перехват необработанной ошибки
Запуск!
в консоли мы в любом случае как-то понимаем, что приложение начало запускаться;
Debug — ну, это, скорее всего IDE и тут тоже понятно.
Вот и получается, что:
это не даёт понимания о начале запуска приложения, так как оно уже запущено на этот момент и находится в процессе инициализации прикладных механизмов (инициализация объекта хоста, сервисов, загрузка конфигурации и прочее);
Перехват необработанной ошибки - что получаем?
Перехват ошибки запуска приложения, как написано в тексте ошибки. Попробуем проверить как это будет выглядеть.
Проведём пару тестов, в которых запустим веб-приложение а консоли и в методе конфигурации сервисов выкинем ошибку.
Вывод в консоли:
Вывод в консоль при отлове необработанного исключение
Теперь закомментируем перехват необработанной ошибки в Main:
Вывод в консоли:
Вывод в консоль без отлова необработанной ошибки
Даже без логирования через Serilog, необработанное исключение было выведено в консоль.
Перехват необработанной ошибки - что может случиться?
Тест1: ошибка на этапе конфигурирования сервисов. В этом тесте ошибка происходит при конфигуировании сервисов приложения.
Успешное окончание
Вывод по логированию в Main
Лог-записи о начале запуска приложения и окончании работы приложения — это зона ответственности инфраструктуры, которая запускает и фиксирует факт остановки приложения. Само приложение должно отчитаться о том, что оно успешно запущено и готово к работе, и, возможно, что начинает процедуру останова.
По опыту, ошибки на этапе запуска веб-приложения — это редкая ситуация, которая быстро выявляется и устраняется, потому что при запуске приложения в основном формируется и конфигурируется объектная модель приложения. Самые, наверное, вероятные ситуации:
нет какой-то библиотеки
[ADMN] LogManager v0.2.3 - плагин для управления лог файла bukkit [1.2.5-r1+]
Плагин LogManager дает возможность работать с log-файлом сервера bukkit. Плагин в основном нацелен на работу в крупных серверах.
Установка:
Распаковать архив
Положить в папку с плагинами.
Запустить сервер;
Остановить сервер;
Заменить messages.yml на русский, если вам не нужен плагин на английском языке
Готово
[ru_RU] Russian => скачать
[en_US] English => автоматически генерируется при отсутствии messages.yml
/logclear - удалить всё содержимое лог файла
/logsize - показать размер лог файла
/logdelete - удалить содержимое лог файла во время выключения сервера.
/loginfo - вывести в чат информацию о плагине, о разработчиках плагина , описание и т.д (Описание не полностью реализовано)
/logbackup - создание копии лог файла в отдельной папке (не работает)
Версии:
0.2.3
- Оптимизация на 10 %
- Введена поддержка мультиязычности
- Исправлена проблема с путями во всех OC
- Компиляция под bukkit 1.2.5 и выше
0.2.2
- Исправлена команда /logsize .Теперь она работает
- Улучшена команда /logdelete (теперь при выключении сервера лог файл удаляется, а не чистится )
- Резервирование команды /logBackup (Не работает)
- Оптимизация на 20 %
Для лучшей работы плагина советую установить:
1. Ansicon - цветная консоль + возможность писать по-русски (на windows)
О том как его установить смотрим здесь
2. Java 7 обязательно !
Дополнения:
LogManagerScript - Независимая от bukkit модификация плагина LogManager
Глобальный логгер — Как это работает?
Как написано в примере Program.cs перед инициализацией статического логгера:
The initial «bootstrap» logger is able to log errors during start-up. It's completely replaced by the logger configured in UseSerilog() below, once configuration and dependency-injection have both been set up successfully.
Т.е. инициализируется глобальный логгер, который действует до успешного вызова UseSerilog() при создании HostBuilder приложения. После чего он будет заменён на логгер, собранный в соответствии с конфигурацией приложения.
Глобальный логгер в Serilog - свойство Logger статического класса Log с get и set. Имеет значение по умолчанию — объект типа SilentLogger, который ничего никуда не пишет:
Теперь исследуем UseSerilog и где назначается глобальный логгер.
Проходим в UseSerilog — это где-то там назначается глобальный логгер. Что делает этот метод:
определяет, можно ли переконфигурировать текущий глобальный логгер. Нужно переконфигурировать (перезагрузить в терминах кода), если верны все условия:
глобальный логгер является логгером, который можно переконфигурировать после его создания, т.е. он наследуется от ReloadableLogger;
входной параметр preserveStaticLogger (не трогать статический (читай глобальный) логгер) == false ;
если необходимо «перезагрузить» глобальный логгер, то для дальнейших целей используется переконфигурированный глобальный логгер. В противном случае на основе конфига приложения создаётся новый логгер;
По умолчанию preserveStaticLogger==false, поэтому глобальный статический логгер по умолчанию заменяется. Как это происходит:
в фабрике создаётся поставщик логгеров Serilog и туда передаётся логгер — тоже null . Этот поставщик используется фабрикой, чтобы предоставить объект логгера, реализующий платформенный интерфейс Microsoft.Extensions.Logging.ILogger ;
в платформенном логгере Serilog в конструкторе происходит выбор используемого логгера Serilog : если в качестве логгера передан null , то в дальнейшем в качестве логгера Serilog будет использоваться глобальный статический логгер из Log.Logger . И уже этот объект будет использоваться для логирования непосредственно в методе логирования этого платформенного логгера Serilog : тут и тут.
Содержание
Глобальный логгер — Вред
Глобальный логгер — это объект, через который осуществляется логирование в Serilog, присвоенный публичному статическому свойству Logger статического класса Serilog.Log . Это свойство инициализируется при запуске приложения с конфигурацией в коде, а затем второй раз инициализируется при инициализации хоста веб-приложения на основе загруженной конфигурации.
Таким образом это не Singleton в чистом виде. Но в пределах работы веб-приложения он задумывается как единственный глобальный экземпляр.
с логированием в логгер из последнего теста
«а ну чё, и так же работает»;
как следствие, использование в коде статического сервис-локатора - антипаттерн;
т.к. статическое свойство Logger изменяемое, то есть вероятность несанкционированного замены логгера в непредназначенных для этого местах. Например, это могут быть временные костыли, которые имеют особенность становиться постоянными;
Заключение
К сожалению, простым запросом слияния в github проблему с глобальным логом полностью не решить. Тут требуется глобальный рефакторинг, который может, например включать такие изменения:
Введение
Статистика популярности log4net и NLog 2012-2014 гг.
Читайте также: