Статьи

Log4j 2.x XSD не является полностью описательным

В блоге JAXB и XML-файлах конфигурации Log4j я обсуждал «нюансы и тонкости, связанные с использованием JAXB для работы с XML-файлами конфигурации [Log4j 1.x и Log4j 2.x] через классы Java». В этой статье я рассмотрю еще одну проблему, связанную с созданием XML-конфигурации Log4j 2.x с помощью объектов JAXB, сгенерированных из файла XML-схемы Log4j 2.x Log4j-config.xsd : он не полностью определяет компоненты Log4j 2.x Конфигурационные характеристики.

При работе с конфигурацией XML Log4j 2.x одним из первых важных отличий является то, какой « вкус » XML следует использовать («сжатый» или «строгий»). Краткий формат может быть проще, потому что имена элементов XML соответствуют компонентам Log4j 2, которые они представляют, но XSD поддерживает только строгий формат. Здесь подразумевается, что любой XML, маршалируемый из объектов JAXB, созданных из XSD Log4j 2.x, обязательно будет иметь «строгий» формат, а не «сжатый» формат.

К сожалению, XSD ( Log4j-config.xsd ), в настоящее время предоставляемый с дистрибутивом Log4j 2.x, недостаточно для генерации полной «строгой» конфигурации XML, поддерживаемой Log4j 2. Я демонстрирую это здесь с обсуждением определяемого XSD сложного типа « AppenderType », потому что это один из наиболее экстремальных случаев, когда поддерживаемому элементу не хватает спецификации его потенциальных атрибутов в XSD. В приведенном ниже листинге кода показано определение AppenderType в Log4j-config.xsd AppenderType с версии Log4j 2.6.2 .

AppenderType определен в Log4j-config.xsd Log4j 2.6.2

01
02
03
04
05
06
07
08
09
10
11
12
<xs:complexType name="AppenderType">
   <xs:sequence>
      <xs:element name="Layout" type="LayoutType" minOccurs="0"/>
      <xs:choice minOccurs="0" maxOccurs="1">
         <xs:element name="Filters" type="FiltersType"/>
         <xs:element name="Filter" type="FilterType"/>
      </xs:choice>
   </xs:sequence>
   <xs:attribute name="type" type="xs:string" use="required"/>
   <xs:attribute name="name" type="xs:string" use="required"/>
   <xs:attribute name="fileName" type="xs:string" use="optional"/>
</xs:complexType>

Выдержка из только что показанного XSD говорит нам о том, что аппендир, описанный в XSD-совместимом XML, сможет иметь только один или несколько из трех атрибутов ( type , name и name fileName ). Атрибут « type » используется для определения типа используемого им приложения (например, « File », « RollingFile », « Console », « Socket » и « Syslog »). Проблема заключается в том, что каждый «тип» appender имеет разные свойства и характеристики, которые в идеале должны быть описаны атрибутами этого AppenderType .

В документации Log4j 2.x на Appenders перечислены характеристики различных типов дополнений. Например, эта страница указывает, что ConsoleAppender имеет семь параметров: filter , layout , follow , direct , name , ignoreExceptions и target . name является одним из атрибутов, поддерживаемых общим сложным типом AppenderType а filter и layout поддерживаются с помощью вложенных элементов в этом AppenderType . Однако остальные четыре параметра, доступные для ConsoleAppender , не имеют механизма, предписанного в XSD для их определения.

Даже без учета пользовательских приложений Log4j 2.x встроенные приложения Log4j 2.x не имеют одинаковых атрибутов и характеристик, и большинство из них имеют больше характеристик, чем указанные три атрибута и два вложенных элемента AppenderType . Ранее я обсуждал семь параметров Console Appender, а другие примеры включают RollingFileAppender с его двенадцатью параметрами ( append , bufferedIO , bufferSize , filter , fileName , filePattern , immediateFlush filePattern , layout , name , policy , strategy , ignoreExceptions ), JDBCAppender с его семью параметры ( name , ignoreExceptions , filter , bufferSize , connectionSource , tableName , columnConfigs ) и JMSAppender с его тринадцатью параметрами ( factoryName , factoryName , filter , layout , name , password , providerURL , destinationBindingName , securityPrincipalName , securityCredentials , ignoreExceptions , urlPkgPrefixes ).

Для описания каждого параметра, доступного для данного типа приложения в XSD, потребуется возможность в XML-схеме записать, что конкретный набор доступных атрибутов зависит от настройки AppenderType type AppenderType . К сожалению, XML-схема не всегда поддерживает этот тип условной спецификации, в которой доступные атрибуты данного сложного типа различаются в зависимости от одного из других атрибутов сложного типа.

Из-за ограничений языка схемы человеку, желающему использовать JAXB для генерации объектов с полной поддержкой всех предоставленных дополнений, потребуется изменить XSD. Один из подходов состоит в том, чтобы изменить XSD таким образом, чтобы в AppenderType были все возможные атрибуты любого из встроенных дополнений, доступных в качестве необязательных атрибутов для элемента. Наиболее очевидным недостатком этого является то, что XSD позволит любому типу приложения иметь любой атрибут, даже если этот атрибут не применяется к определенному типу приложения. Однако этот подход позволил бы сгенерированным JAXB объектам маршалировать все атрибуты XML для данного типа аппендера. Следующий фрагмент кода иллюстрирует, как это может быть начато. Здесь указаны некоторые дополнительные атрибуты, в которых нуждаются разные аппендеры, но даже этот более длинный список не содержит все возможные атрибуты аппендера, необходимые для поддержки атрибутов всех возможных встроенных типов аппендеров.

Некоторые атрибуты Appender добавлены в AppenderType

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<xs:complexType name="AppenderType">
   <xs:sequence>
      <xs:element name="Layout" type="LayoutType" minOccurs="0"/>
      <xs:choice minOccurs="0" maxOccurs="1">
         <xs:element name="Filters" type="FiltersType"/>
         <xs:element name="Filter" type="FilterType"/>
      </xs:choice>
   </xs:sequence>
   <xs:attribute name="type" type="xs:string" use="required"/>
   <xs:attribute name="name" type="xs:string" use="required"/>
   <xs:attribute name="fileName" type="xs:string" use="optional"/>
   <!-- Attributes specified below here are not in Log4j 2.x Log4j-config.xsd -->
   <xs:attribute name="target" type="xs:string" use="optional"/>
   <xs:attribute name="follow" type="xs:string" use="optional"/>
   <xs:attribute name="append" type="xs:string" use="optional"/>
   <xs:attribute name="filePattern" type="xs:string" use="optional"/>
   <xs:attribute name="host" type="xs:string" use="optional"/>
   <xs:attribute name="port" type="xs:string" use="optional"/>
   <xs:attribute name="protocol" type="xs:string" use="optional"/>
   <xs:attribute name="connectTimeoutMillis" type="xs:integer" use="optional"/>
   <xs:attribute name="reconnectionDelayMillis" type="xs:string" use="optional"/>
   <xs:attribute name="facility" type="xs:string" use="optional"/>
   <xs:attribute name="id" type="xs:string" use="optional"/>
   <xs:attribute name="enterpriseNumber" type="xs:integer" use="optional"/>
   <xs:attribute name="useMdc" type="xs:boolean" use="optional"/>
   <xs:attribute name="mdcId" type="xs:string" use="optional"/>
   <xs:attribute name="mdcPrefix" type="xs:string" use="optional"/>
   <xs:attribute name="eventPrefix" type="xs:string" use="optional"/>
   <xs:attribute name="newLine" type="xs:boolean" use="optional"/>
   <xs:attribute name="newLineEscape" type="xs:string" use="optional"/>
</xs:complexType>

Второй подход к изменению XSD Log4j 2.x для полной поддержки всех встроенных приложений заключается в том, чтобы изменить конструкцию XSD с одного AppenderType , определенный тип которого был задан атрибутом type на множество различных сложных типов, каждый из которых представляет разные встроенные типы приложений. При таком подходе все атрибуты для любого данного приложения и только атрибуты, связанные с этим данным приложением, могут быть принудительно применены XSD. Этот подход к наличию типа элемента для каждого аппендера аналогичен тому, как работает «сжатый» формат XML, но в настоящее время поддержка XSD для него отсутствует.

Обратите внимание, что я намеренно сфокусировался на типах встроенных приложений, потому что это то, что статический XSD может ожидать разумно, адекватно и полностью поддерживать. Кроме того: это может быть поддержано путем указания произвольных пар имя / значение для атрибутов, как это делается для фильтров или с параметрами , но это также приводит к возможности указывать дополнительные и даже бессмысленные атрибуты без какой-либо возможности схемы их перехватить. Третий подход, который будет поддерживать пользовательские типы, состоит в том, чтобы не использовать статический XSD для описания грамматики, а вместо этого использовать сгенерированный XSD. Можно написать такую ​​XSD вручную на основе описаний компонентов Log4j 2.x в документации, но лучшим подходом может быть использование преимуществ аннотаций @PluginFactory , @PluginElement и @PluginAttribute, используемых в Log4j 2.x. исходный код. Следующие два листинга кода взяты из базы кода Apache Log4j 2.6.2 и демонстрируют, как эти аннотации описывают элементы и атрибуты данных типов.

ConsoleAppender.createAppender () Подпись

1
2
3
4
5
6
7
8
@PluginFactory
public static ConsoleAppender createAppender(
   @PluginElement("Layout") Layout layout,
   @PluginElement("Filter") final Filter filter,
   @PluginAttribute(value = "target", defaultString = "SYSTEM_OUT") final String targetStr,
   @PluginAttribute("name") final String name,
   @PluginAttribute(value = "follow", defaultBoolean = false) final String follow,
   @PluginAttribute(value = "ignoreExceptions", defaultBoolean = true) final String ignore)

SysLogAppender.createAppender () Подпись

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
@PluginFactory
public static SyslogAppender createAppender(
   // @formatter:off
   @PluginAttribute("host") final String host,
   @PluginAttribute(value = "port", defaultInt = 0) final int port,
   @PluginAttribute("protocol") final String protocolStr,
   @PluginElement("SSL") final SslConfiguration sslConfig,
   @PluginAttribute(value = "connectTimeoutMillis", defaultInt = 0) final int connectTimeoutMillis,
   @PluginAliases("reconnectionDelay") // deprecated
   @PluginAttribute(value = "reconnectionDelayMillis", defaultInt = 0) final int reconnectionDelayMillis,
   @PluginAttribute(value = "immediateFail", defaultBoolean = true) final boolean immediateFail,
   @PluginAttribute("name") final String name,
   @PluginAttribute(value = "immediateFlush", defaultBoolean = true) final boolean immediateFlush,
   @PluginAttribute(value = "ignoreExceptions", defaultBoolean = true) final boolean ignoreExceptions,
   @PluginAttribute(value = "facility", defaultString = "LOCAL0") final Facility facility,
   @PluginAttribute("id") final String id,
   @PluginAttribute(value = "enterpriseNumber", defaultInt = Rfc5424Layout.DEFAULT_ENTERPRISE_NUMBER) final int enterpriseNumber,
   @PluginAttribute(value = "includeMdc", defaultBoolean = true) final boolean includeMdc,
   @PluginAttribute("mdcId") final String mdcId,
   @PluginAttribute("mdcPrefix") final String mdcPrefix,
   @PluginAttribute("eventPrefix") final String eventPrefix,
   @PluginAttribute(value = "newLine", defaultBoolean = false) final boolean newLine,
   @PluginAttribute("newLineEscape") final String escapeNL,
   @PluginAttribute("appName") final String appName,
   @PluginAttribute("messageId") final String msgId,
   @PluginAttribute("mdcExcludes") final String excludes,
   @PluginAttribute("mdcIncludes") final String includes,
   @PluginAttribute("mdcRequired") final String required,
   @PluginAttribute("format") final String format,
   @PluginElement("Filter") final Filter filter,
   @PluginConfiguration final Configuration config,
   @PluginAttribute(value = "charset", defaultString = "UTF-8") final Charset charsetName,
   @PluginAttribute("exceptionPattern") final String exceptionPattern,
   @PluginElement("LoggerFields") final LoggerFields[] loggerFields, @PluginAttribute(value = "advertise", defaultBoolean = false) final boolean advertise)

Этот подход требует нескольких шагов, потому что нужно было бы динамически генерировать XSD, используя знания основных компонентов архитектуры Log4j 2.x в сочетании с обработкой аннотаций, а затем использовать JAXB для генерации классов Java, способных маршалировать всеобъемлющий Log4j 2.x. XML.

Другой вариант, который следует рассмотреть, — это использовать «сжатый» XML или другую форму конфигурации Log4j 2.x (например, JSON или файлы свойств ) и не использовать XSD для генерации объектов JAXB для маршалинга конфигурации Log4j 2.x. Стоит отметить, что файлы конфигурации XML, используемые для Log4j 2.x со «строгим» форматом, очевидно, не нуждаются в проверке по Log4j-config.xsd иначе «строгая» форма XML не сможет полностью указать Log4j2 конфигурации. Следствием этого является то, что оставшееся значение даже наличия XSD заключается либо в том, чтобы наши собственные инструменты или сценарии использовали его для проверки нашей конфигурации XML перед использованием его с Log4j 2.x, либо для использования при маршалинге / демаршалинге Log4j 2.x XML с JAXB.

Вывод

Log4j-config.xsd поставляемый с дистрибутивом Log4j2, недостаточен для проверки всех конструкций Log4j 2.x в «строгой» конфигурации XML и также недостаточен для генерации объектов JAXB для использования для маршалирования строгого XML в Log4j2. Разработчики, желающие использовать XSD для проверки или генерации класса JAXB, должны будут вручную изменить XSD или сгенерировать его из исходного кода Log4j2.

Дополнительные ссылки

Эти ссылки были связаны с постами в посте выше, но перечислены здесь для акцента.