Log4J
Getting started
Spring installation
- Add dependencies:
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.20.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.20.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>- edit pom.xml, add the following
exclusion:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframewoork.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>This will disable Spring Logback.
- Create a file named
log4j2-spring.xmlunder/src/main/resources
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</Console>
</Appenders>
<Loggers>
<Root level="trace">
<AppenderRef ref="console" />
</Root>
</Loggers>
</Configuration>- Simple example code:
package com.example;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class App {
protected static final Logger logger = LogManager.getLogger();
public static void main(String[] args) {
logger.info("Hello World!");
}
}Log Level
- Trace - Only when I would be “tracing” the code and trying to find one part of a function specifically.
- Debug - Information that is diagnostically helpful to people more than just developers (IT, sysadmins, etc.).
- Info - Generally useful information to log (service start/stop, configuration assumptions, etc). Info I want to always have available but usually don’t care about under normal circumstances. This is my out-of-the-box config level.
- Warn - Anything that can potentially cause application oddities, but for which I am automatically recovering. (Such as switching from a primary to backup server, retrying an operation, missing secondary data, etc.)
- Error - Any error which is fatal to the operation, but not the service or application (can’t open a required file, missing data, etc.). These errors will force user (administrator, or direct user) intervention. These are usually reserved (in my apps) for incorrect connection strings, missing services, etc.
- Fatal - Any error that is forcing a shutdown of the service or application to prevent data loss (or further data loss). I reserve these only for the most heinous errors and situations where there is guaranteed to have been data corruption or loss.
FATAL is when the sysadmin wakes up, decides he's not paid enough for this, and goes back to sleep.
Writing logs to file
Simple configuration that log both to console and to file
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</Console>
<File name="File" filename="src\main\resources\logs\MyApp.log">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</File>
</Appenders>
<Loggers>
<Root level="trace">
<AppenderRef ref="console" />
<AppenderRef ref="File" />
</Root>
</Loggers>
</Configuration>Write logs to different files
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
<Appenders>
<Console name="console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</Console>
<File name="File1" filename="src\main\resources\logs\FileOne.log">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</File>
<File name="File2" filename="src\main\resources\logs\FileTwo.log">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n" />
</File>
</Appenders>
<Loggers>
<Root level="trace">
<AppenderRef ref="console" />
</Root>
<Logger name="MyLogger1" level="debug">
<AppenderRef ref="File1"/>
</Logger>
<Logger name="MyLogger2" level="debug">
<AppenderRef ref="File2"/>
</Logger>
</Loggers>
</Configuration>Example Java Code:
package com.example;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
public class App {
protected static final Logger logOne = LogManager.getLogger("MyLogger1");
protected static final Logger logTwo = LogManager.getLogger("MyLogger2");
public static void main(String[] args) {
logOne.info("This should be writed to file 1");
logTwo.debug("This should be writed to file 2");
}
}Logging username and/or cookie with Log4j2 and Spring Boot
I tried this on InsecureSite, this is my tech stack:
- Authentication handled with MySQL, Java Hybernate and Java Spring Security
- Log4j2
Example Log4j2 configuration:
<Console name="with-context" targer="SYSTEM_OUT">
<PatternLayout pattern="%d{dd-MM-yyyy HH:mm:ss} [%t] [%-5level %logger{36} - %X %msg%n"/>
</Console>
<Loggers>
<Logger name="with-context" level="trace">
<AppenderRef ref="console-context" />
</Logger>
</Loggers>Example Java code:
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.ThreadContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest.
@RestController
public class ExampleLoggingController {
protected static final Logger loggerWithContext = LogManager.getLogger("console-context");
@GetMapping("/api/logging-test/")
public void exampleLogging(HttpServletRequest request, Principal principal){
if ( principal != null) {
ThreadContext.put(key:"user", value:principal.getName()));
}
if ( request != null && request.getCookies().length > 0) {
//Assuming first cookie is JSESSIONID
Cookie jsessionid = request.getCookies()[0];
ThreadContext.put(jsessionid.getName(), jsessionid.getValue());
}
loggerWithContext.info("Logged request");
ThreadContext.clearAll();
}
}