Как в Spring logger получить

    Разрабатывая приложения используя IoC-контейнер Spring думаю каждый задумывался, а как же «правильнее и красивее» создать логгер. В данной публикации хочу привести несколько примеров решения данной задачи.

    Решение 1


    Получаем логгер напрямую через LoggerFactory:
    @Component
    public class MyBean {
        private static final Logger log = LoggerFactory.getLogger("application");
        ...
    }
    

    Данное решение является классическим, безусловно работающим, но нарушает саму идеологию IoC, ведь нам хочется, что бы работу по созданию логгера выполнил сам контейнер.

    Решение 2


    Получаем логгер из контейнера при помощи Autowired:
    @Component
    public class MyBean {
        @Autowired
        private Logger log;
        ...
    }
    

    Для этого в конфигурации Spring объявляем Bean:
    @EnableAutoConfiguration
    @ComponentScan
    public class ApplicationConfiguration {
        @Bean
        public Logger logger(){
            return LoggerFactory.getLogger("application");
        }
    ...
    }
    

    В данном решении задача по созданию логгера возложена на сам контейнер и укладывается в идеологию IoC, но что же делать, если логгеров в приложении должно быть больше одного?

    Решение 3


    Объявляем каждый логгер в виде отдельного Bean:
    @EnableAutoConfiguration
    @ComponentScan
    public class ApplicationConfiguration {
        @Bean
        @Primary
        public Logger logger(){
            return LoggerFactory.getLogger("application");
        }
    
        @Bean(name = "loggerBean")
        public Logger loggerBean(){
            return LoggerFactory.getLogger("loggerBean");
        }
    ...
    }
    

    Получаем нужный логгер используя соответствующий Qualifier:
    @Component
    public class MyBean {
        @Autowired
        private Logger log;
        @Autowired
        @Qualifier("loggerBean")
        private Logger log2;
        ...
    }
    

    Данное решение является достаточным в большинстве случаев, и использует только готовые средства контейнера. Одним из минусов данного решения является то, что при добавлении нового логгера всегда придется объявлять новый Bean. Есть ли более универсальный способ?

    Решение 4


    Получаем логгер из контейнера при помощи специальной аннотации, назовем ее Logging:
    @Component
    public class MyBean {
        @Logging
        private Logger log;
        @Logging("loggerBean")
        private Logger log2;
    
        ...
    }
    

    Для это собственно необходимо объявить аннотацию:
    @Target({ElementType.FIELD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Logging {
        String value();
    }
    

    Данная аннотация будет указывать контейнеру на то, что необходим логгер с именем переданным в параметр value. Если данный параметр не указан, то логгер будет получен по классу компонента, в нашем случае MyBean.
    Отлично, но контейнер не умеет обрабатывать нашу аннотацию. Давайте его научим, для этого создадим процессор:
    public class LoggingAnnotationProcessor implements BeanPostProcessor {
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) {
            Class clazz = bean.getClass();
            do {
                for (Field field : clazz.getDeclaredFields()) {
                    Logging annotation = field.getAnnotation(Logging.class); 
                    if (annotation!= null) {
                        boolean accessible = field.isAccessible();
                        field.setAccessible(true);
                        try {
                            if(!annotation.value().isEmpty()){
                                field.set(bean, LoggerFactory.getLogger(annotation.value()));
                            } else {
                                field.set(bean, LoggerFactory.getLogger(clazz));
                            }
                        } catch (IllegalAccessException e) {
                            LoggerFactory.getLogger(this.getClass()).error(e.getMessage(), e);
                        }
                        field.setAccessible(accessible);
                    }
                }
                clazz = clazz.getSuperclass();
            } while (clazz != null);
            return bean;
        }
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) {
            return bean;
        }
    
    }
    

    И объявим процессор в конфигурации Spring:
    @EnableAutoConfiguration
    @ComponentScan
    public class ApplicationConfiguration {
        @Bean
        public LoggingAnnotationProcessor loggingAnnotationProcessor(){
            return new LoggingAnnotationProcessor();
        }
    ...
    }
    

    Данное решение является более универсальным, но необходимо дополнительно объявить аннотацию и написать для нее процессор.

    Заключение


    Друзья, предлагай в комментариях ваши варианты решения данной задачи, буду очень рад!

    Исходный код примеров доступен на GitHub
    Поделиться публикацией

    Комментарии 38

      +7
      Мне кажется логгер нужно получать по месту ИМХО.
      Ибо уровень логирования в настройках указать на package проще будет.
        +2
        Плюсану, всегда использовал что-то типа
        class SomeCls {
          private final Logger log = Logger.getLogger(SomeCls.class);
        }
        

        Также подход автора не позволит логировать из static методов.
          +9
          Зато позволит весело словить NPE — если вызов логера произойдет до связывания.
            0
            Не уверен, что это возможно. Спринг бины иницилизирует при старте контексте(кроме lazy).
              +3
              статические инициализаторы, конструкторы класса, сеттеры — все останется без логирования, в юнит-тестах придется инжектить еще и мок-логгер
                +1
                Ну если вы используете статические инициализаторы в Spring, то пожалуй сами виноваты.
                В конструктор логгер можно заинжектить, не проблема. А вот моки да, проблема.
        +2
        Не очень понял, зачем бин для логгера? в тексте не увидел пояснений, которыми руководствуется автор. И сохраняется ли при этом разделение уровней логирования по пакетам/классам?

        В IDEA у себя держу следующий шаблон, который генерит код для Slf4J:
        @SuppressWarnings("unused")
        private static final transient Logger LOG = LoggerFactory.getLogger(CURRENT_CLASS.class.getName());


        но в последнее перебрался на аннотацию @Slf4j из Lombok
          +3
          Зачем static field у вас transient? Они же вроде несереализуемые.
              +1
              Если я правильно понял, это на случай, когда автор напишет кастомный сериализатор, чтобы случайно не сериализовать LOG через рефлексию.
              Правда, я всё равно не понимаю, что помешает обойти transient в сериализаторе, ведь в рефлексии всё доступно.
                0
                это не для защиты против обхода, а скорее для тех, кто смотрит только на transient и не смотрит на static. А в целом не понятно — что плохого в такой избыточности?
                  +1
                  Не знаю насколько это плохо, но если ваш сериализатор сериализует static, то скорее всего нужно дать по шее разработчику этого сериализатора. ИМХО static field никакого отношения к вашим объектам иметь не должны. В этом я даже одобряю scala подход к разделение на class и object.
                    +1
                    Ответ в самом вопросе — избыточность. Избыточность ведёт к неоднозначности.

                    Если в спецификации сказано, что static поля не должны быть сериализованы, значит нужно бить по рукам тех, кто это делает, во время code review.

                    В то же время я понимаю, что это исключительно моё субъективное мнение, и другие могут быть с ним не согласиться.
            0
            Всем рекомендую Lombok и его аннотацию @Sl4j.
              0
              @Slf4j на сколько я помню создает логгер по названию класса, а если необходимо создавать по имени — то не подойдет. Другой вопрос кому как удобнее получать логгер — по классу или по имени, лично у меня есть примеры когда в одних случаях удобнее первый подход, есть когда второй — тут вопрос как личных предпочтений так и зависимости от контекста конкретной задачи =)
                0
                она имеет атрибут "topic", где можно переопределить с класса на имя
                  0
                  отлично, перейду пожалуй на Lombok :)
                  0
                  Если не ошибаюсь, логгер всегда получается по имени. В частном случае имя логгера берётся из полного названия класса.

                  Мне было бы интересно узнать, в каком именно случае вы выбрали подход получения логгера по произвольному имени?
                    0
                    имеется в виду, что в логгере будет в качестве имени — полный путь до класса или просто некое слово.
                +2
                Стоит отметить, что подход универсален и годится не только для логгеров. Наоборот, имхо, логгеры — не самый удачный пример.
                  0
                  Профит этого подхода — вместо `@Autowired` + `@Qualifier` использовать одну аннотацию.
                  Но для каждого класса придётся объявлять свою аннотацию + процессор. Разве это удобно?
                    0
                    Но для каждого класса придётся объявлять свою аннотацию + процессор

                    Разве?

                    И, кстати, хорошо бы вместо @Autowired и Qualifier использовать Inject и Named, а еще бы объединить эти аннотации в одну.
                      0
                      Разве?

                      Конечно.
                      LoggingAnnotationProcessor работает только с `@Logging` аннотацией — создаёт логгер и присваивает его полю с этой аннотацией.

                      На мой взгляд, этот подход — велосипед. Я бы не стал его использовать.

                      А конкретно для логгера, как уже некоторые писали в комментариях, лучше использовать Lombok.
                        0
                        LoggingAnnotationProcessor работает только с `@Logging` аннотацией

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

                        А вот вообще написать несколько процессоров разнообразных аннотаций, декорирующих, скажем, методы, во избежание дублирования кода — идея сама по себе красивая.
                          0
                          написать аннотацию+процессор, использующую некоторую фабрику

                          Добро пожаловать в клуб любителей шаблона ServiceLocator :)

                          написать несколько процессоров разнообразных аннотаций, декорирующих, скажем, методы, во избежание дублирования кода

                          Это же делает AOP. Нужно ли писать велосипед?
                            0
                            Добро пожаловать в клуб любителей шаблона ServiceLocator :)

                            А и верно :)
                            Это же делает AOP.

                            Точно.
                            Но и AOP не предел.

                            А в целом, мне все больше кажется, что уже для каждой заманчивой идеи уже есть своя библиотека, и все самописное окажется велосипедом :)
                            Тот же Lombok уже упоминали.
                            Впрочем, все когда-то было велосипедом, пока не стало достаточно популярным :)
                              0
                              все когда-то было велосипедом, пока не стало достаточно популярным :)

                              Это и заставляет писать новые велосипеды :)
                        0
                        а разве @Autowired и Inject не эквивалентны?
                          0
                          Верно, в последних версиях Спринга это так.
                            0
                            @Autowired — личная аннотация Spring.
                            Inject — спецификация JavaEE.

                            Spring поддерживает оба варианта. Но если переезжать со Spring на другой контейнер, который не поддерживает @Autowired, код придётся редактировать.

                            P. S. Парень, зарегистрировавшийся с логином Inject, наверно икает :)
                              +1
                              Не сталкивался ещё со случаями, когда понадобилось избавиться от Spring и целиком переехать на JavaEE. Можете привести?

                              P. S. Парень, зарегистрировавшийся с логином Inject, наверно икает :)
                              а нечего служебные слова использовать ;)
                                0
                                У меня тоже не было такого опыта. Думаю, это индивидуальный случай, специфичный для проекта.
                                  0
                                  Видимо, у Спринга нет (а, похоже, и не будет) достойных конкурентов.

                                  Например, мне очень когда-то понравился фреймворк Tapestry в его 5ой версии. Но пока парни пилили версию 5.4, он безнадежно устарел, увы…
                                    0
                                    А как же play?
                                      0
                                      Play я ковырял достаточно давно, тогда он меня не впечатлил, особенно тем, что он stateless, а также для раскрытия потенциала нужна scala.
                                        0
                                        Ну скажем так, что scala там не нужна, но код на scala выглядит более кратким. И зачем вам не stateless?)
                      0
                      Автор сам своё творение не использовал что ли…
                      Class.getDeclaredFields() может запросто вернуть (и таки возвращает) кучу автогенерируемых полей и только их, т.к. спринг наследует наши классы, а getDeclaredFields() работает только на один уровень; так что до наших собственных полей дело просто не дойдёт (по крайней мере, не сделает это гарантированно).
                        +1
                        «clazz = clazz.getSuperclass();» дойдет

                      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                      Самое читаемое