- PVSM.RU - https://www.pvsm.ru -
Вопросов о работе сервисов на этапах разработки, тестирования и поддержки очень много и все они на первый взгляд непохожи: «Что произошло?», «Был ли запрос?», «Какой формат даты?», «Почему сервис не отвечает?» и т.д.
Корректно составленный лог сможет подробно ответить на эти и многие другие вопросы абсолютно автономно без участия разработчиков. В стремлении к такой заманчивой цели родилась библиотека логирования Eclair, призванная вести диалог со всеми участниками процесса, не перетягивая на себя слишком много одеяла.
Об одеяле и особенностях решения — далее.
Если вам не очень интересно разбираться в предпосылках, можете сразу перейти к описанию нашего решения [1].
Взять org.slf4j.Logger
(к нему Logback с Appender’ами любой масти) и писать в лог всё, что потребуется. Входы в основные методы, выходы, если нужно, отразить пойманные ошибки, какие-то данные. Это ведь необходимо? Да, но:
static
поле с логгером тоже лень (ладно, это может сделать за нас Lombok). Мы, разработчики, ленивы. И мы прислушиваемся к своей лени, это благородная лень: она настойчиво меняет мир к лучшему.ThreadLocal
– не относящиеся друг к другу данные. Руками и глазами это проконтролировать, осмелюсь утверждать, невозможно.
И вот как мы решаем эти проблемы в своих приложениях.
Eclair – это инструмент, упрощающий написание логируемого кода. Он помогает собрать нужную meta-информацию об исходном коде, связать ее с данными, летящими в приложении в runtime и направить их в привычное вам хранилище лога, породив при этом минимум кода.
Основная цель — сделать лог понятным всем участникам процесса разработки. Поэтому, удобством написания кода польза от Eclair не заканчивается, а только начинается.
Eclair логирует аннотированные методы и параметры:
EclairLogger
: указать логгер, который должен обработать аннотацию, можно по имени, по алиасу или по умолчаниюprivate
методе никогда не сработаетSupplier
: давая возможность инициализировать аргументы «лениво»
Исходный код опубликован у нас в GitHub'е под лицензией Apache 2.0:
github.com/TinkoffCreditSystems/eclair [2]
Для подключения вам потребуется Java 8, Maven и Spring Boot 1.5+. Артефакт размещён в Maven Central Repository:
<dependency>
<groupId>ru.tinkoff</groupId>
<artifactId>eclair-spring-boot-starter</artifactId>
<version>0.8.3</version>
</dependency>
Стартер содержит стандартную реализацию EclairLogger
, использующую инициализированную Spring Boot’ом систему логирования с некоторым выверенным набором настроек.
Здесь приведены некоторые примеры типичного использования библиотеки. Сначала даётся фрагмент кода, затем соответствующий ему лог в зависимости от доступности определенного уровня логирования. Более полный набор примеров можно найти на Wiki проекта в разделе Examples [3].
По умолчанию применяется уровень DEBUG.
@Log
void simple() {
}
Если доступен уровень | … то лог будет таким |
---|---|
TRACE |
DEBUG [] r.t.e.e.Example.simple > |
INFO |
- |
Доступный в текущей локации уровень логирования влияет на подробность лога. Чем «ниже» доступный уровень (т.е. чем ближе к TRACE), тем лог подробнее.
@Log(INFO)
boolean verbose(String s, Integer i, Double d) {
return false;
}
Уровень | Лог |
---|---|
TRACE |
INFO [] r.t.e.e.Example.verbose > s="s", i=4, d=5.6 |
INFO |
INFO [] r.t.e.e.Example.verbose > |
WARN |
- |
Типы логируемых исключений могут быть отфильтрованы. Залогированы будут отобранные исключения и их потомки. В этом примере NullPointerException
будет залогирован на уровне WARN, Exception
на уровне ERROR (по умолчанию), а Error
не будет залогирован совсем (потому что Error
не включён в фильтр первой аннотации @Log.error
и явно исключён из фильтра второй аннотации).
@Log.error(level = WARN, ofType = {NullPointerException.class, IndexOutOfBoundsException.class})
@Log.error(exclude = Error.class)
void filterErrors(Throwable throwable) throws Throwable {
throw throwable;
}
// рассмотрен лог вызовов с разными аргументами
filterErrors(new NullPointerException());
filterErrors(new Exception());
filterErrors(new Error());
Уровень | Лог |
---|---|
TRACE |
WARN [] r.t.e.e.Example.filterErrors ! java.lang.NullPointerException |
ERROR |
ERROR [] r.t.e.e.Example.filterErrors ! java.lang.Exception |
@Log.in(INFO)
void parameterLevels(@Log(INFO) Double d,
@Log(DEBUG) String s,
@Log(TRACE) Integer i) {
}
Уровень | Лог |
---|---|
TRACE |
INFO [] r.t.e.e.Example.parameterLevels > d=9.4, s="v", i=7 |
DEBUG |
INFO [] r.t.e.e.Example.parameterLevels > d=9.4, s="v" |
INFO |
INFO [] r.t.e.e.Example.parameterLevels > 9.4 |
WARN |
- |
«Принтеры», отвечающие за формат распечатки, могут конфигурироваться pre- и post-процессорами. В приведённом примере maskJaxb2Printer
сконфигурирован так, что элементы, соответствующие XPath-выражению "//s"
, маскируются при помощи "********"
. В то же время jacksonPrinter
печатает Dto
«as is».
@Log.out(printer = "maskJaxb2Printer")
Dto printers(@Log(printer = "maskJaxb2Printer") Dto xml,
@Log(printer = "jacksonPrinter") Dto json,
Integer i) {
return xml;
}
Уровень | Лог |
---|---|
TRACE |
DEBUG [] r.t.e.e.Example.printers > |
INFO |
- |
Метод логируется при помощи нескольких логгеров одновременно: логгером по умолчанию (аннотированным при помощи @Primary
) и логгером auditLogger
. Определить несколько логгеров можно, если вы хотите разделить логируемые события не только по уровням (TRACE — ERROR), но и направить их в разные хранилища. Например, основной логгер может писать лог в файл на диск при помощи slf4j, а auditLogger
может писать особый срез данных в отличное хранилище (например в Kafka) в своём специфичном формате.
@Log
@Log(logger = "auditLogger")
void twoLoggers() {
}
MDC, установленные при помощи аннотации, автоматически удаляются после выхода из аннотированного метода. Значение записи в MDC может вычисляться динамически при помощи SpEL. В примере приведены: статичная строка, воспринимаемая константой, вычисление выражения 1 + 1
, обращение к бину jacksonPrinter
, вызов static
метода randomUUID
.
MDC с атрибутом global = true
не удаляются после выхода из метода: как видно единственная запись, оставшаяся в MDC до конца лога, — это sum
.
@Log
void outer() {
self.mdc();
}
@Mdc(key = "static", value = "string")
@Mdc(key = "sum", value = "1 + 1", global = true)
@Mdc(key = "beanReference", value = "@jacksonPrinter.print(new ru.tinkoff.eclair.example.Dto())")
@Mdc(key = "staticMethod", value = "T(java.util.UUID).randomUUID()")
@Log
void mdc() {
self.inner();
}
@Log.in
void inner() {
}
Лог при выполнении приведённого выше кода:
DEBUG [] r.t.e.e.Example.outer >
DEBUG [beanReference={"i":0,"s":null}, sum=2, static=string, staticMethod=01234567-89ab-cdef-ghij-klmnopqrstuv] r.t.e.e.Example.mdc >
DEBUG [beanReference={"i":0,"s":null}, sum=2, static=string, staticMethod=01234567-89ab-cdef-ghij-klmnopqrstuv] r.t.e.e.Example.inner >
DEBUG [beanReference={"i":0,"s":null}, sum=2, static=string, staticMethod=01234567-89ab-cdef-ghij-klmnopqrstuv] r.t.e.e.Example.mdc <
DEBUG [sum=2] r.t.e.e.Example.outer <
Если задавать MDC при помощи аннотации на параметре, то аннотированный параметр доступен как корневой объект evaluation-контекста. Здесь "s"
— это поле класса Dto
с типом String
.
@Log.in
void mdcByArgument(@Mdc(key = "dto", value = "#this")
@Mdc(key = "length", value = "s.length()") Dto dto) {
}
Лог при выполнении приведённого выше кода:
DEBUG [length=8, dto=Dto{i=12, s='password'}] r.t.e.e.Example.mdcByArgument > dto=Dto{i=12, s='password'}
Для «ручного» логирования достаточно внедрить реализацию ManualLogger
. Передаваемые аргументы, реализующие interface Supplier
, будут «развёрнуты» только при необходимости.
@Autowired
private ManualLogger logger;
@Log
void manual() {
logger.info("Eager logging: {}", Math.PI);
logger.debug("Lazy logging: {}", (Supplier) () -> Math.PI);
}
Уровень | Лог |
---|---|
TRACE |
DEBUG [] r.t.e.e.Example.manual > |
INFO |
INFO [] r.t.e.e.Example.manual - Eager logging: 3.141592653589793 |
WARN |
- |
Eclair не знает о том, где вы будете хранить свои логи, насколько подробно и долго. Eclair не знает, как вы планируете пользоваться своим логом. Eclair аккуратно достаёт из вашего приложения всю необходимую вам информацию и перенаправляет её в сконфигурированное вами хранилище.
Пример конфигурации EclairLogger
, направляющего лог в Logback-логгер со специфичным Appender'ом:
@Bean
public EclairLogger eclairLogger() {
LoggerFacadeFactory factory = loggerName -> {
ch.qos.logback.classic.LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
ch.qos.logback.classic.Logger logger = context.getLogger(loggerName);
// Appender<ILoggingEvent> appender = ?
// logger.addAppender(appender);
return new Slf4JLoggerFacade(logger);
};
return new SimpleLogger(factory, LoggingSystem.get(SimpleLogger.class.getClassLoader()));
}
Перед тем, как начать пользоваться Eclair как основным инструментом для логирования, стоит ознакомиться с рядом особенностей этого решения. Эти «особенности» обусловлены тем, что в основе Eclair лежит стандартный для Spring механизм проксирования.
— Скорость выполнения кода, завёрнутого в очередной прокси, незначительно, но упадёт. Для нас эти потери редко бывают существенны. Если встает вопрос о сокращении времени выполнения, есть множество действенных мер по оптимизации. Отказ от удобного информативного лога может быть рассмотрен в качестве одной из мер, но не в первую очередь.
— StackTrace «раздуется» ещё чуть больше. Если вы не привыкли к длинным stackTrace’ам от Spring’овых прокси, для вас это может стать неприятностью. По столь же очевидной причине затруднится отладка проксированных классов.
— Не всякий класс и не всякий метод может быть проксирован: private
методы проксировать не удастся, для логирования цепочки методов в одном bean’е потребуется self, проксировать что-либо, не являющееся bean’ом, вы не сможете и т.д.
Совершенно ясно, что этот инструмент, как и любой другой, нужно уметь применить, чтобы извлечь из него пользу. А этот материал лишь поверхностно освещает сторону, в которую мы решили двигаться в поисках идеального решения.
Критика, мысли, подсказки, ссылки – любое ваше участие в жизни проекта я горячо приветствую! Буду рад, если сочтёте Eclair полезным для своих проектов.
Автор: klapatnyuk
Источник [4]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/java/282339
Ссылки в тексте:
[1] решения: #eclair-decision
[2] github.com/TinkoffCreditSystems/eclair: https://github.com/TinkoffCreditSystems/eclair
[3] Examples: https://github.com/TinkoffCreditSystems/eclair/wiki/Examples
[4] Источник: https://habr.com/post/412871/?utm_campaign=412871
Нажмите здесь для печати.