- Псст, тут есть кто-нибудь?
- Это хаб Groovy.
- Теперь вообще не нужно писать код логирования! Он добавляется автоматически!
- Круто, это сэкономит кучу времени и сил!
Привет!
В предыдущих статьях мы поговорили о логировании и о мета-программировании.
Теперь поговорим о мета-логировании.
Или о семантическом логировании — кому как нравится.
Логгер "Бобина" — это были цветочки. А вот и ягодки: Чёрный ящик.
Вступление
Я начал использовать Groovy в 2009 году.
Тогда же я начал исследования на тему того, как можно структурировать и стандартизировать логирование в программах.
Проблема
Существует методологический пробел в подходе и лучших практиках интеграции логирования в код программы.
Вкратце, нет общепринятого набора правил, помогающих программистам понять:
- Какие данные логировать
- Как структурировать логируемые данные
- Когда логировать
- Где размещать код логирования в коде программы
- Как будут использованы созданные логи
- Зачем вообще тратить время на написание кода логирования
Так же, казалось бы с существованием AST API (Groovy) и CGLIB (Java) — должны были уже появиться средства автоматизации логирования — но нет:
- В Groovy есть пара аннотаций (@Slf4j, Log, и т.д.) которые лишь внедряют объявление и инициализацию переменной логгера.
Это экономит лишь пару строчек кода. Но до недавнего времени не существовало реальной возможности автоматизировать сам код логирования. Программистам приходилось в ручную добавлять строки с логированием в код, что приводило к нежелаемым результатам:
- Захламление кода приложения кодом логирования
- Неструктурированный и захламлённый вывод логов
- Человеческий фактор: легко упустить важный вывод в логе, неправильно расположив его логирование
- Удорожание разработки и увеличение сроков
Решение
Теоретические изыскания привели к созданию концепции "семантического логирования" — похожей на функционал в JUL (sourceMethodName), но гораздо более продуманной:
Семантическое Логирование
это техника написания кода, позволяющая отслеживать ход исполнения программы в соответствии с семантическими структурами её кода, такими как:
- Начало и конец выполнения метода (включая конструкторы)
- Аргументы и возвращаемое значение (результат) метода
- Блоки, такие как if/then
- Выражения, например присвоение значения переменной
Дело оставалось за малым: автоматизировать эту концепцию. И тут на помощь пришёл Groovy AST API.
Как сообщество программного обеспечения с Открытым Исходным Кодом, мы взяли на себя инициативу и представили новое решение для Логирования, направленное на решение вышеописанных проблем.
"Чёрный ящик" помогает ответить на эти ключевые вопросы:
- Что логировать:
- 4 уровня детализации:
- Исключения обработки методов (включая аргументы на которых метод выработал исключение)
- Выполнение методов (аргументы, начало и конец выполнение, результат и исключения)
- Блоки внутри методов (поддерживаются все блоки Groovy кода, такие как if, switch, for, и т.д.)
- Вычисление выражений (все выражения Groovy поддерживаются) — с результирующим значением объекта и его классом
- Мета-данные этапа компиляции:
- Номера строк кода
- Непосредственно сам исходный код
- Как структурировать данные логирования
- Данные автоматически структурируются в соответствии с форматом
BlackBox
, не оставляя пространства для неверной интерпретации
- Данные автоматически структурируются в соответствии с форматом
- Где размещать код логирования в коде приложения
- Просто добавьте аннотацию
@BlackBox
к методу (или даже ко всему классу) — и дело сделано. Не нужно захламлять основной код строчками с логированием.
- Просто добавьте аннотацию
- Как будут использованы созданные логи
- Расследование инцидентов и отладка
- Профилирование быстродействие и оптимизация
- Статистика
- Аналитика
- Мониторинг
- Телеметрия
- Журналирование
- Зачем вообще тратить время на написание кода логирования
- А и не нужно!
Назначение
Чёрный ящик
— это решение для автоматической генерации кода Семантического Логирования на Groovy и добавления его в код приложения на этапе компиляции.
В результате это даёт возможность производить и анализировать исчерпывающие данные этапа выполнения приложения в форме логов, основанных на упрощённой классовой модели Groovy AST.
Вкратце
Аннотация @BlackBox
автоматически добавляет детальный код логирования в код приложения, не затрагивая логику самой программы.
Детализация добавляемого логирования может быть настроена пользователем (программистом) до следующих уровней:
- Исключения обработки методов (включая аргументы на которых метод выработал исключение)
- Выполнение методов (аргументы, начало и конец выполнение, результат и исключения)
- Блоки внутри методов (поддерживаются все блоки Groovy кода, такие как if, switch, for, и т.д.)
- Вычисление выражений (все выражения Groovy поддерживаются) — с результирующим значением объекта и его классом
Чёрный ящик
использует Slf4j
-совместимый API логирования, позволяя использовать Ваш любимый логгер:
- Log4j2
- Logback
- Бобина
Мы рекомендуем использовать Slf4j
логгер Бобину вместе с Чёрным ящиком
из-за её простоты и быстродействия.
Чёрный ящик
доступен в Maven
репозитории JCenter
.
Функции
Чёрный ящик
основан на инструменте AST трансформации "Карбюратор".
Для просмотра исчерпывающего списка функций и технического описания, пожалуйста, обращайтесь к документации Карбюратора.
Здесь мы приводим только список дополнительных функций, присущих Чёрному ящику
.
Функции этапа компиляции
Подавление исключений
Существуют сценарии, когда нужно залогировать исключение, но подавить его (проигнорировать) — без перебрасывания.
Например в пользовательских скриптах, в методах постоянно работающих потоков.
Для этого, аннотация @BlackBox
поддерживает параметр suppressExceptions
, который автоматически подавляет необработанные исключения в методе:
@BlackBox(level = CarburetorLevel.ERROR, suppressExceptions = true)
void foo() {
bar()
}
что становится эквивалентно:
void foo() {
try {
bar()
} catch (Exception e) {
log.error(e) //log exception without re-throwing
}
}
Функции этапа выполнения программы
Чёрный ящик
реализует обработку событий времени выполнения API Карбюратора — логируя их с помощью Slf4j
API.
Вот примеры того, как логируется выполнение с помощью Чёрного ящика
:
2019-02-20 18:29:43:121|debug|http-nio-8089-exec-2|conf.plugins.input.rest.SelfTest|METHOD RESULT:
2019-02-20 18:29:43:121|debug|http-nio-8089-exec-2|conf.plugins.input.rest.SelfTest|org.springframework.http.ResponseEntity
2019-02-20 18:29:43:121|debug|http-nio-8089-exec-2|conf.plugins.input.rest.SelfTest|<200 OK OK,io.infinite.pigeon.springdatarest.CustomResponse(response:[message:Self test started (2019-02-20 18-29-43-090), financialMessages:http://localhost:8089/pigeon/outputMessages/search/searchByInputExternalIdAndSourceName?sourceName=SELF_TEST_PLUGIN&externalId=1550672982945, otpMessage:http://localhost:8089/pigeon/outputMessages/search/searchByInputExternalIdAndSourceName?sourceName=SELF_TEST_PLUGIN&externalId=1550672983042]),{}>
2019-02-20 18:29:43:121|debug|http-nio-8089-exec-2|conf.plugins.input.rest.SelfTest|METHOD END: applyPlugin.conf.plugins.input.rest.SelfTest(20,1,62,2)
2019-02-21 15:43:51:128|debug|http-nio-8089-exec-1|conf.plugins.input.rest.SelfTest|METHOD: getResponseJson.conf.plugins.input.rest.SelfTest(71,1,79,2)
2019-02-21 15:43:51:154|debug|http-nio-8089-exec-1|conf.plugins.input.rest.SelfTest|METHOD RESULT:
2019-02-21 15:43:51:154|debug|http-nio-8089-exec-1|conf.plugins.input.rest.SelfTest|org.apache.groovy.json.internal.LazyMap
2019-02-21 15:43:51:155|debug|http-nio-8089-exec-1|conf.plugins.input.rest.SelfTest|[message:Self test started (2019-02-21 15-43-51-148), financialMessages:http://localhost:8089/pigeon/outputMessages/search/searchByInputExternalIdAndSourceName?sourceName=SELF_TEST_PLUGIN&externalId=1550749431056, otpMessage:http://localhost:8089/pigeon/outputMessages/search/searchByInputExternalIdAndSourceName?sourceName=SELF_TEST_PLUGIN&externalId=1550749431124]
2019-02-21 15:43:51:155|debug|http-nio-8089-exec-1|conf.plugins.input.rest.SelfTest|METHOD END: getResponseJson.conf.plugins.input.rest.SelfTest(71,1,79,2)
2019-02-21 15:48:34:536|debug|http-nio-8089-exec-1|conf.plugins.input.rest.SelfTest|METHOD: getResponseJson.conf.plugins.input.rest.SelfTest(71,1,79,2)
2019-02-21 15:48:34:571|debug|http-nio-8089-exec-1|conf.plugins.input.rest.SelfTest|METHOD RESULT:
2019-02-21 15:48:34:571|debug|http-nio-8089-exec-1|conf.plugins.input.rest.SelfTest|org.apache.groovy.json.internal.LazyMap
2019-02-21 15:48:34:572|debug|http-nio-8089-exec-1|conf.plugins.input.rest.SelfTest|[message:Self test started (2019-02-21 15-48-34-564), financialMessages:http://localhost:8089/pigeon/outputMessages/search/searchByInputExternalIdAndSourceName?sourceName=SELF_TEST_PLUGIN&externalId=1550749714444, otpMessage:http://localhost:8089/pigeon/outputMessages/search/searchByInputExternalIdAndSourceName?sourceName=SELF_TEST_PLUGIN&externalId=1550749714530]
2019-02-21 15:48:34:572|debug|http-nio-8089-exec-1|conf.plugins.input.rest.SelfTest|METHOD END: getResponseJson.conf.plugins.input.rest.SelfTest(71,1,79,2)
2019-02-21 15:53:58:299|debug|http-nio-8089-exec-1|conf.plugins.input.rest.SelfTest|METHOD: getResponseJson.conf.plugins.input.rest.SelfTest(71,1,79,2)
2019-02-21 15:53:58:326|debug|http-nio-8089-exec-1|conf.plugins.input.rest.SelfTest|METHOD RESULT:
2019-02-21 15:53:58:326|debug|http-nio-8089-exec-1|conf.plugins.input.rest.SelfTest|org.apache.groovy.json.internal.LazyMap
2019-02-21 15:53:58:327|debug|http-nio-8089-exec-1|conf.plugins.input.rest.SelfTest|[message:Self test started (2019-02-21 15-53-58-318), financialMessages:http://localhost:8089/pigeon/outputMessages/search/searchByInputExternalIdAndSourceName?sourceName=SELF_TEST_PLUGIN&externalId=1550750038217, otpMessage:http://localhost:8089/pigeon/outputMessages/search/searchByInputExternalIdAndSourceName?sourceName=SELF_TEST_PLUGIN&externalId=1550750038295]
2019-02-21 15:53:58:328|debug|http-nio-8089-exec-1|conf.plugins.input.rest.SelfTest|METHOD END: getResponseJson.conf.plugins.input.rest.SelfTest(71,1,79,2)
2019-02-21 15:58:35:032|debug|http-nio-8089-exec-1|conf.plugins.input.rest.SelfTest|METHOD: getResponseJson.conf.plugins.input.rest.SelfTest(71,1,79,2)
2019-02-21 15:58:35:061|debug|http-nio-8089-exec-1|conf.plugins.input.rest.SelfTest|METHOD RESULT:
2019-02-21 15:58:35:061|debug|http-nio-8089-exec-1|conf.plugins.input.rest.SelfTest|org.apache.groovy.json.internal.LazyMap
2019-02-21 15:58:35:062|debug|http-nio-8089-exec-1|conf.plugins.input.rest.SelfTest|[message:Self test started (2019-02-21 15-58-35-052), financialMessages:http://localhost:8089/pigeon/outputMessages/search/searchByInputExternalIdAndSourceName?sourceName=SELF_TEST_PLUGIN&externalId=1550750314954, otpMessage:http://localhost:8089/pigeon/outputMessages/search/searchByInputExternalIdAndSourceName?sourceName=SELF_TEST_PLUGIN&externalId=1550750315028]
2019-02-21 15:58:35:063|debug|http-nio-8089-exec-1|conf.plugins.input.rest.SelfTest|METHOD END: getResponseJson.conf.plugins.input.rest.SelfTest(71,1,79,2)
2019-02-21 15:54:11:486|error|SELF_TEST_RETRY_OUTPUT_RETRY_SENDER_1_RETRY|io.infinite.pigeon.threads.SenderThread|EXCEPTION (first occurrence):
2019-02-21 15:54:11:492|error|SELF_TEST_RETRY_OUTPUT_RETRY_SENDER_1_RETRY|io.infinite.pigeon.threads.SenderThread|a64aceed-8e4c-4d4b-8030-0c23bc3b3f0d
2019-02-21 15:54:11:493|error|SELF_TEST_RETRY_OUTPUT_RETRY_SENDER_1_RETRY|io.infinite.pigeon.threads.SenderThread|java.lang.NullPointerException
at io.infinite.pigeon.threads.SenderThread.sendMessage(SenderThread.groovy:98)
at io.infinite.pigeon.threads.SenderThread.run(SenderThread.groovy:44)
2019-02-21 15:54:11:493|error|SELF_TEST_RETRY_OUTPUT_RETRY_SENDER_1_RETRY|io.infinite.pigeon.threads.SenderThread|METHOD EXCEPTION: sendMessage.io.infinite.pigeon.threads.SenderThread(60,5,100,6)
2019-02-21 15:54:11:493|error|SELF_TEST_RETRY_OUTPUT_RETRY_SENDER_1_RETRY|io.infinite.pigeon.threads.SenderThread|EXCEPTION (additional occurrence):
2019-02-21 15:54:11:494|error|SELF_TEST_RETRY_OUTPUT_RETRY_SENDER_1_RETRY|io.infinite.pigeon.threads.SenderThread|a64aceed-8e4c-4d4b-8030-0c23bc3b3f0d
2019-02-21 15:54:11:494|error|SELF_TEST_RETRY_OUTPUT_RETRY_SENDER_1_RETRY|io.infinite.pigeon.threads.SenderThread|METHOD EXCEPTION: run.io.infinite.pigeon.threads.SenderThread(40,5,50,6)
Использование
Чёрный ящик
размещён в репозитории JCenter
:
Gradle
...
repositories {
jcenter()
}
...
compile 'io.infinite:blackbox:1.0.21'
...
Аннотация @BlackBox
применима к классам, методам и конструкторам:
import io.infinite.blackbox.BlackBox
import io.infinite.carburetor.CarburetorLevel
@BlackBox(level = CarburetorLevel.EXPRESSION)
String foobar(String foo) {
String bar = "bar"
String foobar = foo + bar
return foobar
}
foobar("foo")
Заключение
Чёрный ящик
— это новое секретное оружие Groovy, а также фундамент открытой экосистемы i-t.io.
По нашим оценкам, использование Чёрного ящика
экономит до 25% ресурсов на этапе написания программного кода и от 20% до 500% при поддержке программ.
Это уникальная реализация инновационной концепции, подкреплённая научным подходом к её разработке.
Забудьте о логировании — теперь Чёрный ящик
сделает его за вас.
В следующей статье мы расскажем о том, как собираемся привлечь студентов и развивающихся стран (Бангладеш) к работе над Open Source проектами.