[BAEL-2111] guest | Why SLF4J? 10 Reasons to use it (#5536)
* Added new submodule for SLF4J guest post, containg modules for logback, log4j and log4j2 * * added tests for logging endpoints * removed application tests, no use now that we have other tests
This commit is contained in:
parent
c10101a9ac
commit
be22bd209b
|
@ -0,0 +1,64 @@
|
|||
<?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>
|
||||
|
||||
<groupId>com.stackify.slf4j.guide</groupId>
|
||||
<artifactId>slf4j-parent-module</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.0.6.RELEASE</version>
|
||||
</parent>
|
||||
|
||||
<modules>
|
||||
<module>slf4j-logback</module>
|
||||
<module>slf4j-log4j2</module>
|
||||
<module>slf4j-log4j</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-module-junit4</artifactId>
|
||||
<version>${powermock.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.powermock</groupId>
|
||||
<artifactId>powermock-api-mockito2</artifactId>
|
||||
<version>${powermock.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<java.version>1.8</java.version>
|
||||
<powermock.version>2.0.0-beta.5</powermock.version>
|
||||
</properties>
|
||||
</project>
|
|
@ -0,0 +1,25 @@
|
|||
/target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/build/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
|
@ -0,0 +1,41 @@
|
|||
<?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>
|
||||
|
||||
<artifactId>slf4j-log4j</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>com.stackify.slf4j.guide</groupId>
|
||||
<artifactId>slf4j-parent-module</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-log4j12</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<properties>
|
||||
<slf4j.log4j.version>1.7.25</slf4j.log4j.version>
|
||||
</properties>
|
||||
</project>
|
|
@ -0,0 +1,12 @@
|
|||
package com.stackify.slf4j.guide;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package com.stackify.slf4j.guide.controllers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class SimpleController {
|
||||
|
||||
Logger logger = LoggerFactory.getLogger(SimpleController.class);
|
||||
|
||||
@GetMapping("/slf4j-guide-request")
|
||||
public String processList(List<String> list) {
|
||||
logger.info("Client requested process the following list: {}", list);
|
||||
try {
|
||||
logger.debug("Starting process");
|
||||
// ...processing list here...
|
||||
Thread.sleep(500);
|
||||
} catch (RuntimeException | InterruptedException e) {
|
||||
logger.error("There was an issue processing the list.", e);
|
||||
} finally {
|
||||
logger.info("Finished processing");
|
||||
}
|
||||
return "done";
|
||||
}
|
||||
|
||||
@GetMapping("/slf4j-guide-mdc-request")
|
||||
public String clientMDCRequest(@RequestHeader String clientId) throws InterruptedException {
|
||||
MDC.put("clientId", clientId);
|
||||
logger.info("Client {} has made a request", clientId);
|
||||
logger.info("Starting request");
|
||||
Thread.sleep(500);
|
||||
logger.info("Finished request");
|
||||
MDC.clear();
|
||||
return "finished";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
|
||||
|
||||
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
|
||||
<appender name="console" class="org.apache.log4j.ConsoleAppender">
|
||||
<param name="Target" value="System.out"/>
|
||||
<layout class="org.apache.log4j.PatternLayout">
|
||||
<param name="ConversionPattern" value="%5p %d{ISO8601} [%X{clientId}@%t][%x] %c - %m%n"/>
|
||||
</layout>
|
||||
</appender>
|
||||
|
||||
<root>
|
||||
<priority value ="info" />
|
||||
<appender-ref ref="console" />
|
||||
</root>
|
||||
|
||||
</log4j:configuration>
|
|
@ -0,0 +1,80 @@
|
|||
package com.stackify.slf4j.guide.controllers;
|
||||
|
||||
import static org.powermock.api.mockito.PowerMockito.doNothing;
|
||||
import static org.powermock.api.mockito.PowerMockito.spy;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.apache.log4j.Level;
|
||||
import org.apache.log4j.spi.LoggingEvent;
|
||||
import org.assertj.core.api.Condition;
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
import org.powermock.modules.junit4.PowerMockRunner;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
import com.stackify.slf4j.guide.utils.ListAppender;
|
||||
|
||||
@RunWith(PowerMockRunner.class)
|
||||
@PrepareForTest(MDC.class)
|
||||
public class SimpleControllerIntegrationTest {
|
||||
|
||||
private SimpleController controller = new SimpleController();
|
||||
|
||||
@Before
|
||||
public void clearLogList() {
|
||||
ListAppender.clearEventList();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenSimpleRequestMade_thenAllRegularMessagesLogged() {
|
||||
String output = controller.processList(Collections.emptyList());
|
||||
|
||||
SoftAssertions errorCollector = new SoftAssertions();
|
||||
errorCollector.assertThat(ListAppender.getEvents())
|
||||
.haveAtLeastOne(eventContains("Client requested process the following list: []", Level.INFO))
|
||||
.haveAtLeastOne(eventContains("Starting process", Level.DEBUG))
|
||||
.haveAtLeastOne(eventContains("Finished processing", Level.INFO))
|
||||
.haveExactly(0, eventOfLevel(Level.ERROR));
|
||||
errorCollector.assertThat(output)
|
||||
.isEqualTo("done");
|
||||
errorCollector.assertAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenClientId_whenMDCRequestMade_thenMessagesWithClientIdLogged() throws Exception {
|
||||
// We avoid cleaning the context so tht we can check it afterwards
|
||||
spy(MDC.class);
|
||||
doNothing().when(MDC.class);
|
||||
MDC.clear();
|
||||
String clientId = "id-1234";
|
||||
|
||||
String output = controller.clientMDCRequest(clientId);
|
||||
|
||||
SoftAssertions errorCollector = new SoftAssertions();
|
||||
errorCollector.assertThat(ListAppender.getEvents())
|
||||
.allMatch(entry -> {
|
||||
return clientId.equals(entry.getMDC("clientId"));
|
||||
})
|
||||
.haveAtLeastOne(eventContains("Client id-1234 has made a request", Level.INFO))
|
||||
.haveAtLeastOne(eventContains("Starting request", Level.INFO))
|
||||
.haveAtLeastOne(eventContains("Finished request", Level.INFO));
|
||||
errorCollector.assertThat(output)
|
||||
.isEqualTo("finished");
|
||||
errorCollector.assertAll();
|
||||
|
||||
}
|
||||
|
||||
private Condition<LoggingEvent> eventOfLevel(Level level) {
|
||||
return eventContains(null, level);
|
||||
}
|
||||
|
||||
private Condition<LoggingEvent> eventContains(String substring, Level level) {
|
||||
|
||||
return new Condition<LoggingEvent>(entry -> (substring == null || (entry.getRenderedMessage() != null && entry.getRenderedMessage()
|
||||
.contains(substring))) && (level == null || level.equals(entry.getLevel())), String.format("entry with message '%s', level %s", substring, level));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
package com.stackify.slf4j.guide.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.log4j.AppenderSkeleton;
|
||||
import org.apache.log4j.spi.LoggingEvent;
|
||||
|
||||
public class ListAppender extends AppenderSkeleton {
|
||||
public static List<LoggingEvent> events = new ArrayList<LoggingEvent>();
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean requiresLayout() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void append(LoggingEvent event) {
|
||||
events.add(event);
|
||||
}
|
||||
|
||||
public static List<LoggingEvent> getEvents() {
|
||||
return events;
|
||||
}
|
||||
|
||||
public static void clearEventList() {
|
||||
events.clear();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
|
||||
|
||||
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
|
||||
<appender name="console" class="org.apache.log4j.ConsoleAppender">
|
||||
<param name="Target" value="System.out"/>
|
||||
</appender>
|
||||
<appender name="list" class="com.stackify.slf4j.guide.utils.ListAppender">
|
||||
</appender>
|
||||
|
||||
<root>
|
||||
<priority value ="trace" />
|
||||
<appender-ref ref="list" />
|
||||
</root>
|
||||
|
||||
</log4j:configuration>
|
|
@ -0,0 +1,25 @@
|
|||
/target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/build/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
|
@ -0,0 +1,54 @@
|
|||
<?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>
|
||||
|
||||
<artifactId>slf4j-log4j2</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>com.stackify.slf4j.guide</groupId>
|
||||
<artifactId>slf4j-parent-module</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-logging</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-log4j2</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-core</artifactId>
|
||||
<version>${log4j.version}</version>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.logging.log4j</groupId>
|
||||
<artifactId>log4j-api</artifactId>
|
||||
<version>${log4j.version}</version>
|
||||
<type>test-jar</type>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<log4j.version>2.11.0</log4j.version>
|
||||
</properties>
|
||||
</project>
|
|
@ -0,0 +1,12 @@
|
|||
package com.stackify.slf4j.guide;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package com.stackify.slf4j.guide.controllers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.MDC;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class SimpleController {
|
||||
|
||||
Logger logger = LoggerFactory.getLogger(SimpleController.class);
|
||||
|
||||
@GetMapping("/slf4j-guide-request")
|
||||
public String processList(List<String> list) {
|
||||
logger.info("Client requested process the following list: {}", list);
|
||||
try {
|
||||
logger.debug("Starting process");
|
||||
// ...processing list here...
|
||||
Thread.sleep(500);
|
||||
} catch (RuntimeException | InterruptedException e) {
|
||||
logger.error("There was an issue processing the list.", e);
|
||||
} finally {
|
||||
logger.info("Finished processing");
|
||||
}
|
||||
return "done";
|
||||
}
|
||||
|
||||
@GetMapping("/slf4j-guide-mdc-request")
|
||||
public String clientMDCRequest(@RequestHeader String clientId) throws InterruptedException {
|
||||
MDC.put("clientId", clientId);
|
||||
logger.info("Client {} has made a request", clientId);
|
||||
logger.info("Starting request");
|
||||
Thread.sleep(500);
|
||||
logger.info("Finished request");
|
||||
MDC.clear();
|
||||
return "finished";
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
<Configuration status="WARN">
|
||||
<Properties>
|
||||
<Property name="PID">????</Property>
|
||||
<Property name="LOG_LEVEL_PATTERN">%5p</Property>
|
||||
<Property name="LOG_DATEFORMAT_PATTERN">yyyy-MM-dd HH:mm:ss.SSS</Property>
|
||||
<Property name="CONSOLE_LOG_PATTERN">%clr{%d{${LOG_DATEFORMAT_PATTERN}}}{faint} %clr{${LOG_LEVEL_PATTERN}} %clr{${sys:PID}}{magenta}
|
||||
%clr{---}{faint} %clr{%X{clientId}}{red}%clr{@%15.15t}{faint} %clr{%-40.40c{1.}}{cyan} %clr{:}{faint} %m%n
|
||||
</Property>
|
||||
</Properties>
|
||||
<Appenders>
|
||||
<Console name="Console" target="SYSTEM_OUT" follow="true">
|
||||
<PatternLayout pattern="${sys:CONSOLE_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.eclipse.jetty.util.component.AbstractLifeCycle" level="error" />
|
||||
<Logger name="org.hibernate.validator.internal.util.Version" level="warn" />
|
||||
<logger name="org.springframework.boot.actuate.endpoint.jmx" level="warn" />
|
||||
<Root level="info">
|
||||
<AppenderRef ref="Console" />
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
|
@ -0,0 +1,81 @@
|
|||
package com.stackify.slf4j.guide.controllers;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.apache.logging.log4j.Level;
|
||||
import org.apache.logging.log4j.core.LogEvent;
|
||||
import org.apache.logging.log4j.junit.LoggerContextRule;
|
||||
import org.apache.logging.log4j.test.appender.ListAppender;
|
||||
import org.assertj.core.api.Condition;
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.ClassRule;
|
||||
import org.junit.Test;
|
||||
|
||||
public class SimpleControllerIntegrationTest {
|
||||
|
||||
private static ListAppender appender;
|
||||
private SimpleController controller = new SimpleController();
|
||||
|
||||
@ClassRule
|
||||
public static LoggerContextRule init = new LoggerContextRule("log4j2-test.xml");
|
||||
|
||||
@BeforeClass
|
||||
public static void setupLogging() {
|
||||
appender = init.getListAppender("ListAppender");
|
||||
}
|
||||
|
||||
@Before
|
||||
public void clearAppender() {
|
||||
appender.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenSimpleRequestMade_thenAllRegularMessagesLogged() {
|
||||
String output = controller.processList(Collections.emptyList());
|
||||
|
||||
SoftAssertions errorCollector = new SoftAssertions();
|
||||
errorCollector.assertThat(appender.getEvents())
|
||||
.haveAtLeastOne(eventContains("Client requested process the following list: []", Level.INFO))
|
||||
.haveAtLeastOne(eventContains("Starting process", Level.DEBUG))
|
||||
.haveAtLeastOne(eventContains("Finished processing", Level.INFO))
|
||||
.haveExactly(0, eventOfLevel(Level.ERROR));
|
||||
errorCollector.assertThat(output)
|
||||
.isEqualTo("done");
|
||||
errorCollector.assertAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenClientId_whenMDCRequestMade_thenMessagesWithClientIdLogged() throws Exception {
|
||||
String clientId = "id-1234";
|
||||
|
||||
String output = controller.clientMDCRequest(clientId);
|
||||
|
||||
SoftAssertions errorCollector = new SoftAssertions();
|
||||
errorCollector.assertThat(appender.getEvents())
|
||||
.allMatch(entry -> {
|
||||
return clientId.equals(entry.getContextData()
|
||||
.getValue("clientId"));
|
||||
})
|
||||
.haveAtLeastOne(eventContains("Client id-1234 has made a request", Level.INFO))
|
||||
.haveAtLeastOne(eventContains("Starting request", Level.INFO))
|
||||
.haveAtLeastOne(eventContains("Finished request", Level.INFO));
|
||||
errorCollector.assertThat(output)
|
||||
.isEqualTo("finished");
|
||||
errorCollector.assertAll();
|
||||
}
|
||||
|
||||
private Condition<LogEvent> eventOfLevel(Level level) {
|
||||
return eventContains(null, level);
|
||||
}
|
||||
|
||||
private Condition<LogEvent> eventContains(String substring, Level level) {
|
||||
|
||||
return new Condition<LogEvent>(entry -> (substring == null || (entry.getMessage()
|
||||
.getFormattedMessage() != null && entry.getMessage()
|
||||
.getFormattedMessage()
|
||||
.contains(substring)))
|
||||
&& (level == null || level.equals(entry.getLevel())), String.format("entry with message '%s', level %s", substring, level));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Configuration status="warn" strict="true" name="SLF4JTests">
|
||||
|
||||
<Appenders>
|
||||
<Appender type="Console" name="STDOUT" />
|
||||
<Appender type="List" name="ListAppender" />
|
||||
</Appenders>
|
||||
|
||||
<Loggers>
|
||||
<Logger name="com.stackify" level="trace" additivity="false">
|
||||
<AppenderRef ref="ListAppender"/>
|
||||
</Logger>
|
||||
|
||||
<Root level="trace">
|
||||
<AppenderRef ref="STDOUT"/>
|
||||
</Root>
|
||||
</Loggers>
|
||||
</Configuration>
|
|
@ -0,0 +1,25 @@
|
|||
/target/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### IntelliJ IDEA ###
|
||||
.idea
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### NetBeans ###
|
||||
/nbproject/private/
|
||||
/build/
|
||||
/nbbuild/
|
||||
/dist/
|
||||
/nbdist/
|
||||
/.nb-gradle/
|
|
@ -0,0 +1,32 @@
|
|||
<?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>
|
||||
|
||||
<artifactId>slf4j-logback</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<parent>
|
||||
<groupId>com.stackify.slf4j.guide</groupId>
|
||||
<artifactId>slf4j-parent-module</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<relativePath>..</relativePath>
|
||||
</parent>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-ext</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ch.qos.cal10n</groupId>
|
||||
<artifactId>cal10n-api</artifactId>
|
||||
<version>${cal10n.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<cal10n.version>0.8.1</cal10n.version>
|
||||
</properties>
|
||||
</project>
|
|
@ -0,0 +1,12 @@
|
|||
package com.stackify.slf4j.guide;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(Application.class, args);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
package com.stackify.slf4j.guide.controllers;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.slf4j.MDC;
|
||||
import org.slf4j.Marker;
|
||||
import org.slf4j.MarkerFactory;
|
||||
import org.slf4j.cal10n.LocLogger;
|
||||
import org.slf4j.cal10n.LocLoggerFactory;
|
||||
import org.slf4j.ext.EventData;
|
||||
import org.slf4j.ext.EventLogger;
|
||||
import org.slf4j.profiler.Profiler;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.stackify.slf4j.guide.l10n.Messages;
|
||||
|
||||
import ch.qos.cal10n.IMessageConveyor;
|
||||
import ch.qos.cal10n.MessageConveyor;
|
||||
|
||||
@RestController
|
||||
public class SimpleController {
|
||||
|
||||
Logger logger = LoggerFactory.getLogger(SimpleController.class);
|
||||
|
||||
@GetMapping("/slf4j-guide-request")
|
||||
public String processList(List<String> list) {
|
||||
logger.info("Client requested process the following list: {}", list);
|
||||
try {
|
||||
logger.debug("Starting process");
|
||||
// ...processing list here...
|
||||
Thread.sleep(500);
|
||||
} catch (RuntimeException | InterruptedException e) {
|
||||
logger.error("There was an issue processing the list.", e);
|
||||
} finally {
|
||||
logger.info("Finished processing");
|
||||
}
|
||||
return "done";
|
||||
}
|
||||
|
||||
@GetMapping("/slf4j-guide-mdc-request")
|
||||
public String clientMDCRequest(@RequestHeader String clientId) throws InterruptedException {
|
||||
MDC.put("clientId", clientId);
|
||||
logger.info("Client {} has made a request", clientId);
|
||||
logger.info("Starting request");
|
||||
Thread.sleep(500);
|
||||
logger.info("Finished request");
|
||||
MDC.clear();
|
||||
return "finished";
|
||||
}
|
||||
|
||||
@GetMapping("/slf4j-guide-marker-request")
|
||||
public String clientMarkerRequest() throws InterruptedException {
|
||||
logger.info("client has made a request");
|
||||
Marker myMarker = MarkerFactory.getMarker("MYMARKER");
|
||||
logger.info(myMarker, "Starting request");
|
||||
Thread.sleep(500);
|
||||
logger.debug(myMarker, "Finished request");
|
||||
return "finished";
|
||||
}
|
||||
|
||||
@GetMapping("/slf4j-guide-profiler-request")
|
||||
public String clientProfilerRequest() {
|
||||
logger.info("client has made a request");
|
||||
Profiler myProfiler = new Profiler("MYPROFILER");
|
||||
// Associate the logger to handle the output( for testing purposes here)
|
||||
myProfiler.setLogger(logger);
|
||||
|
||||
myProfiler.start("List generation process");
|
||||
List<Integer> list = generateList();
|
||||
|
||||
myProfiler.start("List sorting process");
|
||||
Collections.sort(list);
|
||||
|
||||
// Use the log() method instead of print() to use the logger (for testing purposes here)
|
||||
myProfiler.stop()
|
||||
.log();
|
||||
return "finished";
|
||||
}
|
||||
|
||||
private List<Integer> generateList() {
|
||||
List<Integer> generated = new ArrayList<>();
|
||||
for (int i = 0; i < 5000; i++) {
|
||||
generated.add(ThreadLocalRandom.current()
|
||||
.nextInt(2000));
|
||||
}
|
||||
return generated;
|
||||
}
|
||||
|
||||
@GetMapping("/slf4j-guide-event-request")
|
||||
public String clientEventRequest(@RequestParam("sender") String sender, @RequestParam("receiver") String receiver) {
|
||||
logger.info("sending from {} to {}", sender, receiver);
|
||||
|
||||
// ...sending process...
|
||||
|
||||
EventData data = new EventData();
|
||||
data.setEventDateTime(new Date());
|
||||
data.setEventType("sending");
|
||||
String confirm = UUID.randomUUID()
|
||||
.toString();
|
||||
data.setEventId(confirm);
|
||||
data.put("from", sender);
|
||||
data.put("to", receiver);
|
||||
EventLogger.logEvent(data);
|
||||
|
||||
return "finished";
|
||||
}
|
||||
|
||||
@GetMapping("/slf4j-guide-locale-request")
|
||||
public String clientLocaleRequest(@RequestHeader("Accept-Language") String localeHeader) {
|
||||
List<Locale.LanguageRange> list = Locale.LanguageRange.parse(localeHeader);
|
||||
Locale locale = Locale.lookup(list, Arrays.asList(Locale.getAvailableLocales()));
|
||||
IMessageConveyor messageConveyor = new MessageConveyor(locale);
|
||||
LocLoggerFactory llFactory = new LocLoggerFactory(messageConveyor);
|
||||
LocLogger locLogger = llFactory.getLocLogger(this.getClass());
|
||||
locLogger.info(Messages.CLIENT_REQUEST, "parametrizedClientId", localeHeader);
|
||||
locLogger.debug(Messages.REQUEST_STARTED);
|
||||
locLogger.info(Messages.REQUEST_FINISHED);
|
||||
return "finished";
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package com.stackify.slf4j.guide.l10n;
|
||||
|
||||
import ch.qos.cal10n.BaseName;
|
||||
import ch.qos.cal10n.Locale;
|
||||
import ch.qos.cal10n.LocaleData;
|
||||
|
||||
@BaseName("messages")
|
||||
@LocaleData({ @Locale("en_US"), @Locale("es_ES") })
|
||||
public enum Messages {
|
||||
CLIENT_REQUEST, REQUEST_STARTED, REQUEST_FINISHED
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package com.stackify.slf4j.guide.xlogger;
|
||||
|
||||
import org.slf4j.ext.XLogger;
|
||||
import org.slf4j.ext.XLoggerFactory;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
public class XLoggerController {
|
||||
|
||||
private XLogger logger = XLoggerFactory.getXLogger(XLoggerController.class);
|
||||
|
||||
@GetMapping("/slf4j-guide-xlogger-request")
|
||||
public Integer clientXLoggerRequest(@RequestParam("queryParam") Integer queryParam) {
|
||||
logger.info("Starting process");
|
||||
logger.entry(queryParam);
|
||||
Integer rest = 0;
|
||||
try {
|
||||
rest = queryParam % 3;
|
||||
} catch (RuntimeException anyException) {
|
||||
logger.catching(anyException);
|
||||
}
|
||||
logger.exit(rest);
|
||||
return rest;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder>
|
||||
<pattern>%marker %d{yyyy-MM-dd HH:mm:ss.SSS} -%5p %X{clientId}@%15.15t %-40.40logger{39} : %m%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
<root level="info">
|
||||
<appender-ref ref="CONSOLE" />
|
||||
</root>
|
||||
</configuration>
|
|
@ -0,0 +1,3 @@
|
|||
CLIENT_REQUEST=Client {0} has made a request using locale {1}
|
||||
REQUEST_STARTED=Request started
|
||||
REQUEST_FINISHED=Request finished
|
|
@ -0,0 +1,3 @@
|
|||
CLIENT_REQUEST=El cliente {0} ha realizado una solicitud usando locale {1}
|
||||
REQUEST_STARTED=Solicitud iniciada
|
||||
REQUEST_FINISHED=Solicitud finalizada
|
|
@ -0,0 +1,163 @@
|
|||
package com.stackify.slf4j.guide.controllers;
|
||||
|
||||
import static org.powermock.api.mockito.PowerMockito.doNothing;
|
||||
import static org.powermock.api.mockito.PowerMockito.spy;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.assertj.core.api.Condition;
|
||||
import org.assertj.core.api.SoftAssertions;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||
import org.powermock.modules.junit4.PowerMockRunner;
|
||||
import org.slf4j.MDC;
|
||||
|
||||
import com.stackify.slf4j.guide.utils.ListAppender;
|
||||
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
|
||||
@RunWith(PowerMockRunner.class)
|
||||
@PrepareForTest(MDC.class)
|
||||
public class SimpleControllerIntegrationTest {
|
||||
|
||||
SimpleController controller = new SimpleController();
|
||||
|
||||
@Before
|
||||
public void clearLogList() {
|
||||
ListAppender.clearEventList();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenSimpleRequestMade_thenAllRegularMessagesLogged() {
|
||||
String output = controller.processList(Collections.emptyList());
|
||||
|
||||
SoftAssertions errorCollector = new SoftAssertions();
|
||||
errorCollector.assertThat(ListAppender.getEvents())
|
||||
.haveAtLeastOne(eventContains("Client requested process the following list: []", Level.INFO))
|
||||
.haveAtLeastOne(eventContains("Starting process", Level.DEBUG))
|
||||
.haveAtLeastOne(eventContains("Finished processing", Level.INFO))
|
||||
.haveExactly(0, eventOfLevel(Level.ERROR));
|
||||
errorCollector.assertThat(output)
|
||||
.isEqualTo("done");
|
||||
errorCollector.assertAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenClientId_whenMDCRequestMade_thenMessagesWithClientIdLogged() throws Exception {
|
||||
// We avoid cleaning the context so tht we can check it afterwards
|
||||
spy(MDC.class);
|
||||
doNothing().when(MDC.class);
|
||||
MDC.clear();
|
||||
String clientId = "id-1234";
|
||||
|
||||
String output = controller.clientMDCRequest(clientId);
|
||||
|
||||
SoftAssertions errorCollector = new SoftAssertions();
|
||||
errorCollector.assertThat(ListAppender.getEvents())
|
||||
.allMatch(entry -> {
|
||||
return clientId.equals(entry.getMDCPropertyMap()
|
||||
.get("clientId"));
|
||||
})
|
||||
.haveAtLeastOne(eventContains("Client id-1234 has made a request", Level.INFO))
|
||||
.haveAtLeastOne(eventContains("Starting request", Level.INFO))
|
||||
.haveAtLeastOne(eventContains("Finished request", Level.INFO));
|
||||
errorCollector.assertThat(output)
|
||||
.isEqualTo("finished");
|
||||
errorCollector.assertAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenMarkerRequestMade_thenMessagesWithMarkerLogged() throws Exception {
|
||||
String output = controller.clientMarkerRequest();
|
||||
|
||||
SoftAssertions errorCollector = new SoftAssertions();
|
||||
errorCollector.assertThat(ListAppender.getEvents())
|
||||
.haveAtLeastOne(eventContains("client has made a request", Level.INFO))
|
||||
.haveAtLeastOne(eventContains("Starting request", Level.INFO, "MYMARKER"))
|
||||
.haveAtLeastOne(eventContains("Finished request", Level.DEBUG, "MYMARKER"))
|
||||
.haveExactly(2, eventContains(null, null, "MYMARKER"));
|
||||
errorCollector.assertThat(output)
|
||||
.isEqualTo("finished");
|
||||
errorCollector.assertAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenProfilerRequestMade_thenMessagesWithPerformanceLogged() throws Exception {
|
||||
String output = controller.clientProfilerRequest();
|
||||
|
||||
SoftAssertions errorCollector = new SoftAssertions();
|
||||
errorCollector.assertThat(ListAppender.getEvents())
|
||||
.haveAtLeastOne(eventContains("client has made a request", Level.INFO))
|
||||
.haveAtLeastOne(eventContains("Profiler [MYPROFILER]", Level.DEBUG));
|
||||
errorCollector.assertThat(output)
|
||||
.isEqualTo("finished");
|
||||
errorCollector.assertAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void whenEventRequestMade_thenMessagesWithEventLogged() throws Exception {
|
||||
String sender = "sender";
|
||||
String receiver = "receiver";
|
||||
String output = controller.clientEventRequest(sender, receiver);
|
||||
|
||||
SoftAssertions errorCollector = new SoftAssertions();
|
||||
errorCollector.assertThat(ListAppender.getEvents())
|
||||
.haveAtLeastOne(eventContains("sending from sender to receiver", Level.INFO))
|
||||
.haveAtLeastOne(eventContains("<object class=\"java.util.HashMap\">", Level.INFO))
|
||||
.haveAtLeastOne(eventContains("<string>sender</string>", Level.INFO))
|
||||
.haveAtLeastOne(eventContains("<string>receiver</string>", Level.INFO));
|
||||
errorCollector.assertThat(output)
|
||||
.isEqualTo("finished");
|
||||
errorCollector.assertAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenESLocale_whenLocaleRequestMade_thenMessagesWithEventLogged() throws Exception {
|
||||
String locale = "es-ES";
|
||||
String output = controller.clientLocaleRequest(locale);
|
||||
|
||||
SoftAssertions errorCollector = new SoftAssertions();
|
||||
errorCollector.assertThat(ListAppender.getEvents())
|
||||
.haveAtLeastOne(eventContains("El cliente parametrizedClientId ha realizado una solicitud usando locale es-ES", Level.INFO))
|
||||
.haveAtLeastOne(eventContains("Solicitud iniciada", Level.DEBUG))
|
||||
.haveAtLeastOne(eventContains("Solicitud finalizada", Level.INFO));
|
||||
errorCollector.assertThat(output)
|
||||
.isEqualTo("finished");
|
||||
errorCollector.assertAll();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void givenENLocale_whenLocaleRequestMade_thenMessagesWithEventLogged() throws Exception {
|
||||
String locale = "en-US";
|
||||
String output = controller.clientLocaleRequest(locale);
|
||||
|
||||
SoftAssertions errorCollector = new SoftAssertions();
|
||||
errorCollector.assertThat(ListAppender.getEvents())
|
||||
.haveAtLeastOne(eventContains("Client parametrizedClientId has made a request using locale en-US", Level.INFO))
|
||||
.haveAtLeastOne(eventContains("Request started", Level.DEBUG))
|
||||
.haveAtLeastOne(eventContains("Request finished", Level.INFO));
|
||||
errorCollector.assertThat(output)
|
||||
.isEqualTo("finished");
|
||||
errorCollector.assertAll();
|
||||
}
|
||||
|
||||
private Condition<ILoggingEvent> eventContains(String substring, Level level) {
|
||||
return eventContains(substring, level, null);
|
||||
}
|
||||
|
||||
private Condition<ILoggingEvent> eventOfLevel(Level level) {
|
||||
return eventContains(null, level, null);
|
||||
}
|
||||
|
||||
private Condition<ILoggingEvent> eventContains(String substring, Level level, String markerName) {
|
||||
|
||||
return new Condition<ILoggingEvent>(entry -> (substring == null || (entry.getFormattedMessage() != null && entry.getFormattedMessage()
|
||||
.contains(substring))) && (level == null || level.equals(entry.getLevel())) && (markerName == null || (entry.getMarker() != null
|
||||
&& markerName.equals(entry.getMarker()
|
||||
.getName()))),
|
||||
String.format("entry with message '%s', level %s and marker %s", substring, level, markerName));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package com.stackify.slf4j.guide.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.AppenderBase;
|
||||
|
||||
public class ListAppender extends AppenderBase<ILoggingEvent> {
|
||||
|
||||
static private List<ILoggingEvent> events = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
protected void append(ILoggingEvent eventObject) {
|
||||
events.add(eventObject);
|
||||
}
|
||||
|
||||
public static List<ILoggingEvent> getEvents() {
|
||||
return events;
|
||||
}
|
||||
|
||||
public static void clearEventList() {
|
||||
events.clear();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<appender name="LISTAPPENDER" class="com.stackify.slf4j.guide.utils.ListAppender">
|
||||
</appender>
|
||||
<root level="trace">
|
||||
<appender-ref ref="LISTAPPENDER" />
|
||||
</root>
|
||||
</configuration>
|
Loading…
Reference in New Issue