Showing posts with label Spring Boot. Show all posts
Showing posts with label Spring Boot. Show all posts

Wednesday, August 7, 2019

A Spring Boot based Log4j2 starter project


Spring Boot uses Logback as a default logging solution. In order to switch to Log4j2, there are some configurations that has to be made.

The following customized configurations provide the following features:

A Maven POM for a Spring Boot starter project
Logstash layout dependency for JSon log files to feed into ELK
Log4j configuration with MDC support
Log4j configuration produces multiple log files


<?xml version="1.0" encoding="UTF-8"?>
<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">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>com.urcorp</groupId>
    <artifactId>app-springboot-parent</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
  <artifactId>app-log4j2-starter</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>app-log4j2-starter</name>
  <description>Log4j2 starter</description>

  <properties>
    <log4j2-logstash-layout.version>0.19</log4j2-logstash-layout.version>
  </properties>

  <dependencies>
    <!-- logging -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-log4j2</artifactId>
    </dependency>

    <!-- logstash layout -->
    <dependency>
      <groupId>com.vlkan.log4j2</groupId>
      <artifactId>log4j2-logstash-layout</artifactId>
      <version>${log4j2-logstash-layout.version}</version>
    </dependency>
  </dependencies>

  <profiles>
    <profile>
      <id>local</id>
      <activation>
        <os>
          <family>windows</family>
        </os>
      </activation>
      <properties>
        <log.file.location>C:/app/logs</log.file.location>
      </properties>
    </profile>
    <profile>
      <id>server</id>
      <activation>
        <os>
          <family>!windows</family>
        </os>
      </activation>
      <properties>
        <log.file.location>/app/logs</log.file.location>
      </properties>
    </profile>
  </profiles>
</project>



log4j.xml


<?xml version="1.0" encoding="UTF-8"?>
<Configuration xmlns:xi="http://www.w3.org/2001/XInclude" status="DEBUG" name="log4j2-ur-app" monitorInterval="30">
  <Properties>
    <Property name="logPath">${log.file.location}</Property>
    <Property name="text.log.pattern">%d{yyyy-MM-HH:mm:ss.SSS} [%t - %X{HOSTNAME,APPLICATION,CORRELATION_ID,MESSAGE_ID}] %highlight{%level}{FATAL=bg_red, ERROR=red, WARN=yellow, INFO=green, DEBUG=blue} %c{3} - %m%n</Property>
  </Properties>

  <Appenders>
    <Console name="console" target="SYSTEM_OUT">
      <PatternLayout pattern="${text.log.pattern}" />
    </Console>

    <RollingFile name="serverJson" fileName="${logPath}/server.json"
      filePattern="${logPath}/server_%d{yyyy-MM-dd}.%i.json">
      <LogstashLayout dateTimeFormatPattern="yyyy-MM-dd'T'HH:mm:ss.SSSZZZ"
        templateUri="classpath:LogstashJsonEventLayoutV1.json" prettyPrintEnabled="false" stackTraceEnabled="true" />
      <xi:include href="log4j2-policies.xml" />
      <xi:include href="log4j2-rolloverStrategies.xml" />
    </RollingFile>

    <RollingFile name="serverText" fileName="${logPath}/server.log"
      filePattern="${logPath}/server_%d{yyyy-MM-dd}.log">
      <PatternLayout pattern="${text.log.pattern}" />
      <xi:include href="log4j2-policies.xml" />
      <xi:include href="log4j2-rolloverStrategies.xml" />
    </RollingFile>

    <RollingFile name="exceptionText" fileName="${logPath}/exception.log"
      filePattern="${logPath}/exception_%d{yyyy-MM-dd}.log">
      <Filters>
        <ThresholdFilter level="WARN" onMatch="ACCEPT" />
      </Filters>
      <PatternLayout pattern="${text.log.pattern}" />
      <xi:include href="log4j2-policies.xml" />
      <xi:include href="log4j2-rolloverStrategies.xml" />
    </RollingFile>

    <RollingFile name="eventText" fileName="${logPath}/event.log"
      filePattern="${logPath}/event_%d{yyyy-MM-dd}.log">
      <PatternLayout pattern="${text.log.pattern}" />
      <xi:include href="log4j2-policies.xml" />
      <xi:include href="log4j2-rolloverStrategies.xml" />
    </RollingFile>

    <RollingFile name="statsText" fileName="${logPath}/stats.log"
      filePattern="${logPath}/stats_%d{yyyy-MM-dd}.log">
      <PatternLayout pattern="${text.log.pattern}" />
      <xi:include href="log4j2-policies.xml" />
      <xi:include href="log4j2-rolloverStrategies.xml" />
    </RollingFile>

    <RollingFile name="perfText" fileName="${logPath}/perf.log"
      filePattern="${logPath}/perf_%d{yyyy-MM-dd}.log">
      <PatternLayout pattern="${text.log.pattern}" />
      <xi:include href="log4j2-policies.xml" />
      <xi:include href="log4j2-rolloverStrategies.xml" />
    </RollingFile>

    <RollingFile name="sqlText" fileName="${logPath}/sql.log"
      filePattern="${logPath}/sql_%d{yyyy-MM-dd}.log">
      <PatternLayout pattern="${text.log.pattern}" />
      <xi:include href="log4j2-policies.xml" />
      <xi:include href="log4j2-rolloverStrategies.xml" />
    </RollingFile>
  </Appenders>

  <Loggers>
    <Root level="info" additivity="false">
      <AppenderRef ref="console" />
      <AppenderRef ref="serverText" />
      <AppenderRef ref="serverJson" />
      <AppenderRef ref="exceptionText" />
    </Root>

    <logger name="EVENT" level="info" additivity="false">
      <AppenderRef ref="eventText" />
      <AppenderRef ref="console" />
    </logger>
    <logger name="STATISTICS" level="info" additivity="false">
      <AppenderRef ref="statsText" />
    </logger>
    <logger name="PERFORMANCE" level="info" additivity="false">
      <AppenderRef ref="perfText" />
    </logger>

    <logger name="SQL" level="info" additivity="false">
      <AppenderRef ref="sqlText" />
      <AppenderRef ref="console" />
    </logger>
    <logger name="com.zaxxer.hikari" level="debug" additivity="false">
      <AppenderRef ref="sqlText" />
    </logger>
    <logger name="java.sql" level="debug" additivity="false">
      <AppenderRef ref="sqlText" />
    </logger>
    <logger name="org.apache.ibatis" level="debug" additivity="false">
      <AppenderRef ref="sqlText" />
    </logger>
    <logger name="org.mybatis" level="debug" additivity="false">
      <AppenderRef ref="sqlText" />
    </logger>
    <logger name="com.urcorp.dao.generated.mapper" level="trace" additivity="false">
      <AppenderRef ref="sqlText" />
    </logger>
    <logger name="org.springframework.jdbc.core" level="trace" additivity="true">
      <AppenderRef ref="sqlText" />
    </logger>
  </Loggers>

</Configuration>


log4j2-policies.xml


<?xml version="1.0" encoding="UTF-8"?>
<Policies>
  <OnStartupTriggeringPolicy />
  <SizeBasedTriggeringPolicy size="300 MB" />
  <TimeBasedTriggeringPolicy interval="1"
    modulate="true" />
</Policies>


log4j2-rolloverStrategies.xml


<?xml version="1.0" encoding="UTF-8"?>
<DefaultRolloverStrategy>
  <Delete basePath="${logPath}" maxDepth="2">
    <IfFileName glob="*.(json|log)" />
    <IfLastModified age="7d" />
  </Delete>
</DefaultRolloverStrategy>




package com.urcorp.common.logging;

public enum LogFileTypes {
  EXCEPTION("EXCEPTION."),
  EVENT("EVENT."),
  PERFORMANCE("PERFORMANCE."),
  SERVER("SERVER."),
  SQL("SQL."),
  STATISTICS("STATISTICS.");

  private final String group;

  private LogFileTypes(final String group) {
    this.code = group;
  }

  public String group() {
    return group;
  }
}





A Maven Parent POM for a Spring Boot based application

Spring Boot based Java application is a good choice for new Java applications. The Spring Boot eco-system provides numerous libraries and tools.

In order to better manage the versions of huge amount of libraries, it is a good approach to upgrade the libraries along with Spring Boot upgrades. It will save huge maintenance and integration effort by taking advantage of the work Spring Boot team has already done.

The following sample parent POM provides the following customized features:

  1. removed the Logback and Appache Commons Logging related dependencies
  2. added unit test dependencies
  3. enabled resource filtering
  4. configured Google stype check




<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <!-- This is parent pom for Spring Boot based application. The pom uses spring-boot-dependencies to control dependencies 
    in order to reduce dependency related maintenance effort. The intention is that all applications will go and update along 
    with Spring Boot latest versions and we only define the versions that are required to be overridden here. -->
  <parent>
    <groupId>com.urcorp</groupId>
    <artifactId>urcorp</artifactId>
    <version>1.0</version>
  </parent>

  <groupId>com.urcorp.urdepartment</groupId>
  <artifactId>app-springboot-parent</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <packaging>pom</packaging>
  <name>app-springboot-parent</name>
  <description>Parent POM for Spring Boot based Application</description>

  <properties>
    <maven.build.timestamp.format>yyyy-MM-dd'T'HH:mm:ss'Z'</maven.build.timestamp.format>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

    <!-- lastest versions as of 08/06/2019 -->

    <!-- Spring Boot dependencies -->
    <!-- spring.version from spring-boot.version below: 5.1.7.RELEASE -->
    <spring-boot.version>2.1.5.RELEASE</spring-boot.version>

    <!-- Test scope -->
    <awaitility.version>3.1.6</awaitility.version>

    <!-- Maven plugins -->
    <google.format.version>1.5</google.format.version>
    <junit-vintage-engine.version>5.5.1</junit-vintage-engine.version>
    <maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
    <maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
    <spotless-maven-plugin.version>1.23.0</spotless-maven-plugin.version>
  </properties>

  <dependencyManagement>
    <dependencies>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>${spring-boot.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>

      <!-- Remove dependencies on logback -->
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
        <version>${spring-boot.version}</version>
        <exclusions>
          <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
          </exclusion>
          <exclusion>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-access</artifactId>
          </exclusion>
          <exclusion>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
          </exclusion>
          <exclusion>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
          </exclusion>
          <exclusion>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
          </exclusion>
          <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>log4j-over-slf4j</artifactId>
          </exclusion>
        </exclusions>
      </dependency>

      <!-- Remove dependencies on commons-logging -->
      <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <exclusions>
          <exclusion>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
          </exclusion>
        </exclusions>
      </dependency>
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <!-- ============== Compile & Runtime ============== -->
    <!-- WARNING: be VERY selective. Dependencies you add here are pulled into every sub-module. -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>

    <!-- ==================== Tests ==================== -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>

    <!-- JUnit -->
    <dependency>
      <groupId>org.junit.platform</groupId>
      <artifactId>junit-platform-launcher</artifactId>
      <scope>test</scope>
    </dependency>

    <!-- JUnit 5 -->
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <scope>test</scope>
    </dependency>

    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-params</artifactId>
      <scope>test</scope>
    </dependency>

    <!-- JUnit 4 -->
    <dependency>
      <groupId>org.junit.vintage</groupId>
      <artifactId>junit-vintage-engine</artifactId>
      <scope>test</scope>
    </dependency>
    
    <!-- mock -->
    <dependency>
      <groupId>org.mockito</groupId>
      <artifactId>mockito-inline</artifactId>
      <scope>test</scope>
    </dependency>

    <!-- asynchronous systems testing -->
    <dependency>
      <groupId>org.awaitility</groupId>
      <artifactId>awaitility</artifactId>
      <version>${awaitility.version}</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <resources>
      <resource>
        <directory>src/main/resources</directory>
        <filtering>true</filtering>
      </resource>
      <resource>
        <directory>src/test/resources</directory>
        <filtering>true</filtering>
      </resource>
    </resources>
    <plugins>
      <plugin>
        <groupId>com.diffplug.spotless</groupId>
        <artifactId>spotless-maven-plugin</artifactId>
        <version>${spotless-maven-plugin.version}</version>
        <configuration>
          <java>
            <includes>
              <!-- Include all java files in "src" folder -->
              <include>src/**/*.java</include>
            </includes>
            <googleJavaFormat>
              <!-- Optional, available versions: https://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22com.google.googlejavaformat%22%20AND%20a%3A%22google-java-format%22 -->
              <version>${google.format.version}</version>
              <!-- Optional, available versions: GOOGLE, AOSP https://github.com/google/google-java-format/blob/master/core/src/main/java/com/google/googlejavaformat/java/JavaFormatterOptions.java -->
              <style>GOOGLE</style>
            </googleJavaFormat>
            <removeUnusedImports />
          </java>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>${maven-compiler-plugin.version}</version>
        <configuration>
          <source>${maven.compiler.source}</source>
          <target>${maven.compiler.source}</target>
        </configuration>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>${maven-surefire-plugin.version}</version>
        <configuration>
          <excludes>
          </excludes>
        </configuration>
      </plugin>
    </plugins>
  </build>

</project>