Серия анализа исходного кода Log4j2: (1) загрузка конфигурации

Java

предисловие

В реальных проектах разработки ведение журнала всегда является неизбежной темой. В этой серии статей делается попытка использовать системы журналов slf4j и log4j2 в качестве примеров для анализа принципа работы журналирования с точки зрения исходного кода.

Чтобы изучить структуру журналов, вы должны сначала ознакомиться с различными средами журналов.Вот две рекомендуемые статьи, поэтому я не буду их повторять.

блог woo woo woo.cn на.com/now be/afraid/1…

woo woo .cn blog on.com/elaborate with/afraid…

Для log4j2 существует несколько типов конфигурационных файлов: properties, xml, json/jsn и yaml/yml, чаще всего мы используем xml.

В обычных условиях мы создадим файл log4j2.xml и поместим его в папку /resources проекта. Большинство проектов, которые используют maven для управления зависимостями, также могут быть настроены средой.Разные среды читают разные файлы log4j2, которые обычно находятся в папке /profiles/${env}/.

Большинству людей следует «позаимствовать» из других проектов, скопировать конфигурацию, а затем возиться с ней. Однако рассматривали ли вы:

  1. Зачем писать этот файл конфигурации? Что будет, если не написать?
  2. Существуют ли правила именования этого файла конфигурации? Почему мы обычно видим log4j2.xml вместо других имен?
  3. Как загружается этот файл конфигурации?

Ответы на поставленные выше вопросы и есть первоначальная цель этой статьи.

намекать

1. В этой статье будет использован метод отладки, в качестве основной линии будет взят процесс загрузки конфигурации log4j2 и описан его рабочий процесс, боковые ветки, которые мало на что влияют, будут проигнорированы, а заинтересованные читатели могут самостоятельно проверить исходный код.

2. Предупреждение о мультиизображении! Смотреть лучше с компа.

3. Попробуйте на практике углубить свое понимание.

Подготовка окружающей среды

Прежде чем читать исходный код, обязательно ознакомьтесь с пакетами зависимостей slf4j и log4j2, а также с пакетами адаптации. Взяв в качестве примера maven, пример программы в этой статье представляет:

        <!-- slf4j -->
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.21</version>
        </dependency>

        <!-- bridge -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.7</version>
        </dependency>

        <!-- log4j2 -->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.7</version>
        </dependency>
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.7</version>
        </dependency>

исходный код

Во-первых, мы создаем новый java-файл и ломаем точку, чтобы начать отладку.

Введите метод getLogger. Как видите, конкретная фабрика Logger получается из LoggerFactory.

Введите метод getILoggerFactory.

Забудьте о куче логики здесь, мы закончим на строке 418.

Далее введите реальную ссылку привязки журнала. Поскольку мы представили только log4j2, он будет найден непосредственно здесь, а затем привязан. StaticLoggerBinder находится в пакете log4j2.

Программа переходит к строке 61, и вы можете видеть, что синглтон реализован здесь с использованием голодного метода. Строка 41 создает экземпляр StaticLoggerBinder, который перейдет к строке 53, давайте зайдем и посмотрим подробности.

Видно, что Log4jLoggerFactory наследует абстрактный адаптер журнала AbstractLoggerAdapter. В этом абстрактном адаптере определено несколько методов, не волнуйтесь, они скоро будут упомянуты.

Вернемся к LoggerFactory, через метод getLoggerFactory мы получим только что созданный Log4jLoggerFactory:

Затем мы вводим ссылку getLogger для log4j2.

Вы можете видеть, что getLogger является методом интерфейса и имеет 3 реализации.

Помните Log4jLoggerFactory, которую мы только что получили? AbstractLoggerAdapter — его родительский класс, поэтому мы перейдем к getLogger класса AbstractLoggerAdapter.

getContext — это абстрактный метод AbstractLoggerAdapter, поэтому далее мы перейдем к методу getContext Log4jLoggerFactory.

Здесь мы используем отражение, чтобы найти наш журнал (якорь переводится как «якорь» на китайском языке, что можно понимать как что-то похожее на дескриптор файла). Полученный здесь якорь — «», поэтому он войдет в следующий оператор.

В конце концов мы получим AppClassLoader, и LogManager будет использовать этот загрузчик классов для получения контекста.

Введите getContext, чтобы увидеть:

Обратите внимание: фабрикой здесь является Log4jContextFactory, которая инициализируется в статическом блоке кода в LogManager, подробности будут добавлены позже.

Теперь давайте перейдем к getContext и посмотрим:

Здесь getContext — это метод в интерфейсе ContextSelector, и следующим шагом будет ввод getContext в ClassLoaderContextSelector. Что касается того, как инициализируется селектор, мы поговорим об этом позже.

Следующие шаги являются контекстно-зависимыми операциями, больше не публикуются и в конечном итоге вернутся сюда:

Затем перейдите к методу ctx.start в строке 152, зайдите и посмотрите:

Теперь, наконец, пришло время начать загрузку конфигурации! ! !

Следующие несколько шагов более интуитивны, карта показывает:

Здесь сначала создается экземпляр ConfigurationFactory, а затем получается конфигурация. Что касается создания экземпляра ConfigurationFactory, то здесь это объясняться не будет, но вы можете проверить это сами.

Далее введите метод getConfiguration:

Введите этот метод:

Обратите внимание, что здесь getFactories ясно сообщил нам, что существует 4 фабрики (все они унаследованы от ConfigurationFactory), которые работают с четырьмя типами файлов конфигурации, упомянутыми выше: properties, xml, json/jsn и yaml/yml. Различные суффиксы можно получить, вызвав метод factory.getSupportedTypes(). Возьмите xml в качестве примера:

То же самое справедливо и для других типов файлов.

Что ж, вернемся к способу загрузки конфигурации, вы можете увидеть 426 строк кода, чтобы определить, все ли типы файлов поддерживаются. На самом деле окончательный код ядра — это строки 453–467:

Здесь попробуйте получить конфигурацию с различными условиями.Если конечная конфигурация будет нулевой, будет напечатан журнал ошибок, сообщающий вам, что файл конфигурации не найден. Так как у нас пока нет конфигурации, мы перейдем к строке 466.

Теперь вы можете добавить файл log4j2 в путь /resources, заполнить простую конфигурацию, и вы получите конфигурацию в строке 459. Давайте посмотрим на детали getConfiguration:

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

В качестве примера возьмем самый распространенный файл log4j2.xml:

На картинке выше мы получили имя файла конфигурации: log4j2.xml.

При этом видно, что префикс — log4j2, а суффикс — суффикс файла.

Префикс (строка 505) жестко запрограммирован в ConfigurationFactory:

Поэтому имя файла, определенное при его настройке, должно соответствовать спецификации и не может быть названо произвольно.

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

Внутри метода:

Теперь URL получен. Его значение — «project_path/target/classes/log4j2.xml».

Далее нужно загрузить содержимое из файла (строка 517, что предполагает знание загрузчика классов, проверьте сами).

Затем нужно прочитать содержимое файла xml:

Иди сюда, начни читать файл xml. Эта часть контента будет разбита в следующий раз.

Оставшийся вопрос: как инициализируются фабрика LoggerManager и ее внутренний селектор?

На самом деле перед вызовом LogManager.getContext(cl, false); блок статического кода в LoggerManager будет вызываться заранее. Давайте посмотрим:

Смотрим на 89~100 строчный код:

Введите метод providerutil.getProviders () Внутренний вид:

Вы можете видеть, что провайдер представляет собой синглтон, реализованный лениво (вы обнаружите, что метод ProviderUtil.hasProviders() в 89 строках кода уже был создан, когда он выполняется, поэтому он возвращается прямо здесь. Обратите внимание, что там — деталь в процессе создания, которая будет использована позже), используемая для определения приоритета каждой фабрики.

Давайте сосредоточимся на внутренних деталях 91 строки кода:

В строке 96 класс загружается, а в строке 98 он преобразуется в подкласс LoggerContextFactory (он же Log4jContextFactory).

Итак, вопрос в том, что такое className, почему он указывает Log4jContextFactory?

На самом деле при создании экземпляра Provider ранее конструктор будет читать конфигурационный файл в log4j-core, который содержит атрибуты, соответствующие className:

Таким образом получил className: org.apache.logging.log4j.core.impl.Log4jContextFactory.

Затем спускайтесь вниз:

Вы можете видеть, что экземпляр объекта Log4jContextFactory будет создан путем отражения, и будет вызван конструктор Log4jContextFactory без параметров:

Метод createContextSelector инициализирует селектор:

Последующие детали инициализации не будут расширены.

Закончится здесь:

На этом этапе создается фабрика.

Теперь вы должны быть в состоянии ответить на три вопроса в начале статьи, верно?

Суммировать

В этой статье описывается основная линия загрузки конфигурации журнала log4j2 посредством отладки (без учета многих деталей, таких как настраиваемые пути и т. д.), а в последующих статьях будет дополнительно описан процесс разбора файла конфигурации.

Я надеюсь, что благодаря этой статье читатели смогут глубже понять процесс загрузки конфигурации log4j2.

Наконец, уровень автора ограничен, а ошибки и упущения неизбежны, исправления и обмены приветствуются для общего прогресса.