[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:
rozagerardo 2018-10-28 16:04:31 -03:00 committed by maibin
parent c10101a9ac
commit be22bd209b
31 changed files with 1044 additions and 0 deletions

64
guest/slf4j/guide/pom.xml Normal file
View File

@ -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>

View File

@ -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/

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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";
}
}

View File

@ -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>

View File

@ -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));
}
}

View File

@ -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();
}
}

View File

@ -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>

View File

@ -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/

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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";
}
}

View File

@ -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>

View File

@ -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));
}
}

View File

@ -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>

View File

@ -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/

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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";
}
}

View File

@ -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
}

View File

@ -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;
}
}

View File

@ -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>

View File

@ -0,0 +1,3 @@
CLIENT_REQUEST=Client {0} has made a request using locale {1}
REQUEST_STARTED=Request started
REQUEST_FINISHED=Request finished

View File

@ -0,0 +1,3 @@
CLIENT_REQUEST=El cliente {0} ha realizado una solicitud usando locale {1}
REQUEST_STARTED=Solicitud iniciada
REQUEST_FINISHED=Solicitud finalizada

View File

@ -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));
}
}

View File

@ -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();
}
}

View File

@ -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>