Add log4j-boot-spring module
authorMatt Sicker <boards@gmail.com>
Sat, 14 Jan 2017 23:39:12 +0000 (17:39 -0600)
committerMatt Sicker <boards@gmail.com>
Sat, 14 Jan 2017 23:39:12 +0000 (17:39 -0600)
This is a spring-boot-starter module for bootstrapping Log4j
accordingly with default config files inspired by
spring-boot-starter-log4j2.

pom.xml
spring/pom.xml [new file with mode: 0644]
spring/src/main/java/org/apache/logging/log4j/boot/spring/AutoConfiguringLoggerContextFactory.java [new file with mode: 0644]
spring/src/main/java/org/apache/logging/log4j/boot/spring/Log4jLoggingSystem.java [new file with mode: 0644]
spring/src/main/resources/META-INF/log4j/default/log4j2-file.xml [new file with mode: 0644]
spring/src/main/resources/META-INF/log4j/default/log4j2.xml [new file with mode: 0644]
spring/src/main/resources/log4j2.component.properties [new file with mode: 0644]
spring/src/test/java/org/apache/logging/log4j/boot/spring/LoggingInitializerTest.java [new file with mode: 0644]

diff --git a/pom.xml b/pom.xml
index 415da22..ea5cd77 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -43,6 +43,8 @@
   <url>https://logging.apache.org/log4j/boot/</url>
 
   <properties>
+    <maven.compiler.source>1.7</maven.compiler.source>
+    <maven.compiler.target>1.7</maven.compiler.target>
     <log4jVersion>2.7.1-SNAPSHOT</log4jVersion>
     <slf4jVersion>1.7.22</slf4jVersion>
     <activemqVersion>5.14.1</activemqVersion>
@@ -62,6 +64,7 @@
     <logbackVersion>1.1.8</logbackVersion>
     <kafkaVersion>0.10.1.1</kafkaVersion>
     <mongodbVersion>3.4.1</mongodbVersion>
+    <springbootVersion>1.4.3.RELEASE</springbootVersion>
   </properties>
 
   <modules>
@@ -90,6 +93,7 @@
     <module>layout/xml</module>
     <module>layout/yaml</module>
     <module>script/groovy</module>
+    <module>spring</module>
   </modules>
 
   <scm>
diff --git a/spring/pom.xml b/spring/pom.xml
new file mode 100644 (file)
index 0000000..5985328
--- /dev/null
@@ -0,0 +1,66 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <parent>
+    <artifactId>log4j-boot-parent</artifactId>
+    <groupId>org.apache.logging.log4j.boot</groupId>
+    <version>1.0-SNAPSHOT</version>
+  </parent>
+  <modelVersion>4.0.0</modelVersion>
+
+  <artifactId>log4j-boot-spring</artifactId>
+  <name>Log4j Spring Boot Starter</name>
+  <description>
+    Log4j Boot module for integration with Spring Boot. This module can be used as a complete replacement for
+    spring-boot-starter-logging or spring-boot-starter-log4j2.
+  </description>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.logging.log4j.boot</groupId>
+      <artifactId>log4j-boot-core</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.logging.log4j.boot</groupId>
+      <artifactId>log4j-boot-compat</artifactId>
+      <version>1.0-SNAPSHOT</version>
+      <scope>compile</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot</artifactId>
+      <version>${springbootVersion}</version>
+      <scope>compile</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.springframework.boot</groupId>
+      <artifactId>spring-boot-starter-test</artifactId>
+      <version>${springbootVersion}</version>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/spring/src/main/java/org/apache/logging/log4j/boot/spring/AutoConfiguringLoggerContextFactory.java b/spring/src/main/java/org/apache/logging/log4j/boot/spring/AutoConfiguringLoggerContextFactory.java
new file mode 100644 (file)
index 0000000..706f1db
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.logging.log4j.boot.spring;
+
+import org.apache.logging.log4j.core.impl.Log4jContextFactory;
+import org.springframework.boot.logging.LoggingSystem;
+
+/**
+ * LoggerContextFactory extension to automatically register logging systems.
+ */
+public class AutoConfiguringLoggerContextFactory extends Log4jContextFactory {
+
+    static {
+        System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
+        System.setProperty(LoggingSystem.SYSTEM_PROPERTY, Log4jLoggingSystem.class.getName());
+    }
+
+}
diff --git a/spring/src/main/java/org/apache/logging/log4j/boot/spring/Log4jLoggingSystem.java b/spring/src/main/java/org/apache/logging/log4j/boot/spring/Log4jLoggingSystem.java
new file mode 100644 (file)
index 0000000..e0bad83
--- /dev/null
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.logging.log4j.boot.spring;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.ConfigurationFactory;
+import org.apache.logging.log4j.core.config.LoggerConfig;
+import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
+import org.apache.logging.log4j.core.config.plugins.util.PluginType;
+import org.apache.logging.log4j.core.util.NetUtils;
+import org.apache.logging.log4j.core.util.ReflectionUtil;
+import org.apache.logging.log4j.core.util.Throwables;
+import org.apache.logging.log4j.util.Strings;
+import org.springframework.boot.logging.AbstractLoggingSystem;
+import org.springframework.boot.logging.LogFile;
+import org.springframework.boot.logging.LogLevel;
+import org.springframework.boot.logging.LoggingInitializationContext;
+
+import java.lang.reflect.Method;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Spring Boot LoggingSystem for integration with Log4j 2.
+ */
+public class Log4jLoggingSystem extends AbstractLoggingSystem {
+
+    static {
+        Method factoryIsActive;
+        Method factorySupportedTypes;
+        try {
+            factoryIsActive = ConfigurationFactory.class.getDeclaredMethod("isActive");
+            ReflectionUtil.makeAccessible(factoryIsActive);
+            factorySupportedTypes = ConfigurationFactory.class.getDeclaredMethod("getSupportedTypes");
+            ReflectionUtil.makeAccessible(factorySupportedTypes);
+        } catch (final NoSuchMethodException e) {
+            Throwables.rethrow(e);
+            // unreachable; make the compiler happy
+            factoryIsActive = null;
+            factorySupportedTypes = null;
+        }
+        FACTORY_IS_ACTIVE = factoryIsActive;
+        FACTORY_SUPPORTED_TYPES = factorySupportedTypes;
+    }
+
+    private static final Method FACTORY_IS_ACTIVE;
+    private static final Method FACTORY_SUPPORTED_TYPES;
+
+    private final String[] standardConfigLocations;
+    private LoggerContext loggerContext;
+
+    public Log4jLoggingSystem(final ClassLoader classLoader) {
+        super(classLoader);
+        this.standardConfigLocations = determineStandardConfigLocations();
+    }
+
+    private static String[] determineStandardConfigLocations() {
+        final List<String> locations = new ArrayList<>();
+        for (final ConfigurationFactory factory : findFactories()) {
+            for (final String extension : getSupportedTypes(factory)) {
+                if ("*".equals(extension)) {
+                    continue;
+                }
+                locations.add("log4j2-test" + extension);
+                locations.add("log4j2" + extension);
+            }
+        }
+        return locations.toArray(new String[0]);
+    }
+
+    private static Collection<ConfigurationFactory> findFactories() {
+        final PluginManager manager = new PluginManager(ConfigurationFactory.CATEGORY);
+        manager.collectPlugins();
+        final Collection<ConfigurationFactory> factories = new ArrayList<>();
+        for (final PluginType<?> type : manager.getPlugins().values()) {
+            final ConfigurationFactory factory = tryCreateFactory(type);
+            if (factory != null) {
+                factories.add(factory);
+            }
+        }
+        return factories;
+    }
+
+    private static ConfigurationFactory tryCreateFactory(final PluginType<?> pluginType) {
+        try {
+            return pluginType.getPluginClass().asSubclass(ConfigurationFactory.class).newInstance();
+        } catch (final Exception ignored) {
+            return null;
+        }
+    }
+
+    private static String[] getSupportedTypes(final ConfigurationFactory factory) {
+        try {
+            if ((boolean) FACTORY_IS_ACTIVE.invoke(factory)) {
+                return (String[]) FACTORY_SUPPORTED_TYPES.invoke(factory);
+            }
+        } catch (final Exception ignored) {
+        }
+        return new String[0];
+    }
+
+    @Override
+    protected String[] getStandardConfigLocations() {
+        return standardConfigLocations;
+    }
+
+    @Override
+    protected void loadDefaults(final LoggingInitializationContext context, final LogFile file) {
+        final String configFileName = "classpath:META-INF/log4j/default/log4j2"
+            + ((file == null) ? Strings.EMPTY : "-file")
+            + ".xml";
+        loadConfiguration(context, configFileName, file);
+    }
+
+    @Override
+    protected void loadConfiguration(final LoggingInitializationContext context, final String location,
+                                     final LogFile file) {
+        final URI configLocation = NetUtils.toURI(location);
+        loggerContext = (LoggerContext) LogManager.getContext(
+            getClassLoader(), false, this, configLocation);
+    }
+
+    @Override
+    protected void reinitialize(final LoggingInitializationContext context) {
+        if (loggerContext != null) {
+            loggerContext.reconfigure();
+        }
+    }
+
+    @Override
+    public void cleanUp() {
+        if (loggerContext != null) {
+            loggerContext.setExternalContext(null);
+            loggerContext.terminate();
+        }
+    }
+
+    @Override
+    public void setLogLevel(final String loggerName, final LogLevel logLevel) {
+        if (loggerContext != null) {
+            final Logger logger = loggerContext.getLogger(loggerName);
+            final LoggerConfig config = logger.get();
+            final Level level = convert(logLevel);
+            if (config.getLevel() != level) {
+                config.setLevel(level);
+            }
+        }
+    }
+
+    private static Level convert(final LogLevel logLevel) {
+        switch (logLevel) {
+            case FATAL:
+                return Level.FATAL;
+            case ERROR:
+                return Level.ERROR;
+            case WARN:
+                return Level.WARN;
+            case INFO:
+                return Level.INFO;
+            case DEBUG:
+                return Level.DEBUG;
+            case TRACE:
+                return Level.TRACE;
+            case OFF:
+                return Level.OFF;
+            default:
+                return Level.toLevel(logLevel.name());
+        }
+    }
+}
diff --git a/spring/src/main/resources/META-INF/log4j/default/log4j2-file.xml b/spring/src/main/resources/META-INF/log4j/default/log4j2-file.xml
new file mode 100644 (file)
index 0000000..9880ceb
--- /dev/null
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<Configuration status="WARN">
+       <Properties>
+               <Property name="PID">????</Property>
+               <Property name="LOG_EXCEPTION_CONVERSION_WORD">%xEx</Property>
+               <Property name="LOG_LEVEL_PATTERN">%5p</Property>
+               <Property name="LOG_PATTERN">%d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN} ${sys:PID} --- [%t] %-40.40c{1.} : %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}</Property>
+       </Properties>
+       <Appenders>
+               <Console name="Console" target="SYSTEM_OUT" follow="true">
+                       <PatternLayout pattern="${LOG_PATTERN}" />
+               </Console>
+               <RollingFile name="File" fileName="${sys:LOG_FILE}" filePattern="logs/$${date:yyyy-MM}/app-%d{yyyy-MM-dd-HH}-%i.log.gz">
+                       <PatternLayout pattern="${LOG_PATTERN}"/>
+                       <Policies>
+                               <SizeBasedTriggeringPolicy size="10 MB" />
+                       </Policies>
+               </RollingFile>
+       </Appenders>
+       <Loggers>
+               <Logger name="org.apache.catalina.startup.DigesterFactory" level="error" />
+               <Logger name="org.apache.catalina.util.LifecycleBase" level="error" />
+               <Logger name="org.apache.coyote.http11.Http11NioProtocol" level="warn" />
+               <logger name="org.apache.sshd.common.util.SecurityUtils" level="warn"/>
+               <Logger name="org.apache.tomcat.util.net.NioSelectorPool" level="warn" />
+               <Logger name="org.crsh.plugin" level="warn" />
+               <logger name="org.crsh.ssh" level="warn"/>
+               <Logger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="error" />
+               <Logger name="org.hibernate.validator.internal.util.Version" level="warn" />
+               <logger name="org.springframework.boot.actuate.autoconfigure.CrshAutoConfiguration" level="warn"/>
+               <logger name="org.springframework.boot.actuate.endpoint.jmx" level="warn"/>
+               <logger name="org.thymeleaf" level="warn"/>
+               <Root level="info">
+                       <AppenderRef ref="Console" />
+                       <AppenderRef ref="File" />
+               </Root>
+       </Loggers>
+</Configuration>
diff --git a/spring/src/main/resources/META-INF/log4j/default/log4j2.xml b/spring/src/main/resources/META-INF/log4j/default/log4j2.xml
new file mode 100644 (file)
index 0000000..2ede5aa
--- /dev/null
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Licensed to the Apache Software Foundation (ASF) under one
+  ~ or more contributor license agreements.  See the NOTICE file
+  ~ distributed with this work for additional information
+  ~ regarding copyright ownership.  The ASF licenses this file
+  ~ to you under the Apache License, Version 2.0 (the
+  ~ "License"); you may not use this file except in compliance
+  ~ with the License.  You may obtain a copy of the License at
+  ~
+  ~   http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing,
+  ~ software distributed under the License is distributed on an
+  ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  ~ KIND, either express or implied.  See the License for the
+  ~ specific language governing permissions and limitations
+  ~ under the License.
+  -->
+
+<Configuration status="WARN">
+       <Properties>
+               <Property name="PID">????</Property>
+               <Property name="LOG_EXCEPTION_CONVERSION_WORD">%xEx</Property>
+               <Property name="LOG_LEVEL_PATTERN">%5p</Property>
+               <Property name="LOG_PATTERN">%style{%d{yyyy-MM-dd HH:mm:ss.SSS}}{dim} %highlight{${LOG_LEVEL_PATTERN}} %style{${sys:PID}}{magenta} %style{---}{dim} %style{[%15.15t]}{dim} %style{%-40.40c{1.}}{cyan} %style{:}{faint} %m%n${sys:LOG_EXCEPTION_CONVERSION_WORD}</Property>
+       </Properties>
+       <Appenders>
+               <Console name="Console" target="SYSTEM_OUT" follow="true">
+                       <PatternLayout pattern="${LOG_PATTERN}" />
+               </Console>
+       </Appenders>
+       <Loggers>
+               <Logger name="org.apache.catalina.startup.DigesterFactory" level="error" />
+               <Logger name="org.apache.catalina.util.LifecycleBase" level="error" />
+               <Logger name="org.apache.coyote.http11.Http11NioProtocol" level="warn" />
+               <logger name="org.apache.sshd.common.util.SecurityUtils" level="warn"/>
+               <Logger name="org.apache.tomcat.util.net.NioSelectorPool" level="warn" />
+               <Logger name="org.crsh.plugin" level="warn" />
+               <logger name="org.crsh.ssh" level="warn"/>
+               <Logger name="org.eclipse.jetty.util.component.AbstractLifeCycle" level="error" />
+               <Logger name="org.hibernate.validator.internal.util.Version" level="warn" />
+               <logger name="org.springframework.boot.actuate.autoconfigure.CrshAutoConfiguration" level="warn"/>
+               <logger name="org.springframework.boot.actuate.endpoint.jmx" level="warn"/>
+               <logger name="org.thymeleaf" level="warn"/>
+               <Root level="info">
+                       <AppenderRef ref="Console" />
+               </Root>
+       </Loggers>
+</Configuration>
diff --git a/spring/src/main/resources/log4j2.component.properties b/spring/src/main/resources/log4j2.component.properties
new file mode 100644 (file)
index 0000000..34d00f4
--- /dev/null
@@ -0,0 +1,20 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
+log4j2.loggerContextFactory = org.apache.logging.log4j.boot.spring.AutoConfiguringLoggerContextFactory
\ No newline at end of file
diff --git a/spring/src/test/java/org/apache/logging/log4j/boot/spring/LoggingInitializerTest.java b/spring/src/test/java/org/apache/logging/log4j/boot/spring/LoggingInitializerTest.java
new file mode 100644 (file)
index 0000000..b7002f3
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.logging.log4j.boot.spring;
+
+import org.apache.commons.logging.LogFactory;
+import org.apache.logging.log4j.jcl.LogFactoryImpl;
+import org.apache.logging.slf4j.Log4jLoggerFactory;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.slf4j.ILoggerFactory;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+import java.util.logging.LogManager;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Integration test to verify Spring Boot uses the proper logging facades.
+ */
+@RunWith(SpringRunner.class)
+@SpringBootApplication
+@SpringBootTest
+public class LoggingInitializerTest {
+
+    @Test
+    public void testJavaUtilLogManagerSet() throws Throwable {
+        LogManager logManager = LogManager.getLogManager();
+        assertThat(logManager).isInstanceOf(org.apache.logging.log4j.jul.LogManager.class);
+    }
+
+    @Test
+    public void testCommonsLogFactorySet() throws Throwable {
+        LogFactory logFactory = LogFactory.getFactory();
+        assertThat(logFactory).isInstanceOf(LogFactoryImpl.class);
+    }
+
+    @Test
+    public void testSlf4jFactorySet() throws Exception {
+        ILoggerFactory factory = LoggerFactory.getILoggerFactory();
+        assertThat(factory).isInstanceOf(Log4jLoggerFactory.class);
+    }
+}
\ No newline at end of file