I am observing an issue when sending logs to both log4j2 and logback using kafka.
Here is the code of my very simple Springboot app:
@SpringBootApplication
public class QuestionApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(QuestionApplication.class, args);
HelloWorld helloWorld = new HelloWorld();
HelloWorld.createLogs();
}
}
public class HelloWorld {
private static final Logger logger = LoggerFactory.getLogger(HelloWorld.class);
public static void createLogs() throws JsonProcessingException {
int k = 0;
boolean b = true;
while (b) {
try{
if(Math.random() > 0.5){
logger.info("ISSUE HERE -> custom logs and send log messages to Kafka topic at " +
DateTimeFormatter.ofPattern("HH:mm:ss").format(LocalDateTime.now()));
int i = 2 / 0;
b = k < Integer.MAX_VALUE;
Thread.sleep(1000);
} else{
}
} catch (Exception re){
throw new RuntimeException("ISSUE HERE (custom log) error in try " + k, re);
} finally {
try {
int j = 2 / 0;
} catch (Exception e) {
logger.error("ISSUE HERE (custom log) error in finally " + k, e);
}
}
k++;
}
}
}
Project ONE with just log4j2 + kafka appender
With the above code, in a first project, I need to send the logs to a Kafka topic.
To do so, I am using the following dependencies:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</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-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
</dependency>
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-log4j-appender</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-layout-template-json</artifactId>
</dependency>
And this log4j2.xml file (not logback-spring.xml)
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info" name="spring-boot-kafka-log">
<Appenders>
<Kafka name="Kafkalol" topic="question">
<PatternLayout>
<Pattern>
{
"some field": "1.0",
"parameters": {
"class": "%logger",
"message": "%message"
}
}
</Pattern>
</PatternLayout>
<Property name="bootstrap.servers">localhost:9093</Property>
</Kafka>
<Async name="Async">
<AppenderRef ref="Kafkalol"/>
</Async>
<Console name="stdout" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} %-5p [%-7t] %F:%L - %m%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="INFO">
<AppenderRef ref="Kafkalol"/>
<AppenderRef ref="stdout"/>
</Root>
<Logger name="org.apache.kafka" level="WARN" /><!-- avoid recursive logging -->
</Loggers>
</Configuration>
This WORKS and I am able to see all logs in the kafka topics.
By all logs, I mean my custom logs (from logger.info()) and the springboot logs
Project TWO with just logback + loki appender
With the same above java code, in a second project, I need to send the logs to a loki instance.
To do so, I am using the following dependencies:
<dependency>
<groupId>com.github.loki4j</groupId>
<artifactId>loki-logback-appender</artifactId>
<version>1.6.0-m1</version>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-core</artifactId>
</dependency>
and this is the logback-spring.xml (not log4j2.xml)
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="true">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>
%d{yyyy-MM-dd HH:mm:ss} ${PID} %-5level --- [%thread] [${name},%X{traceId:-},%X{spanId:-}] %logger{36} : %msg%n
</pattern>
</encoder>
</appender>
<appender name="LOKI" class="com.github.loki4j.logback.Loki4jAppender">
<metricsEnabled>true</metricsEnabled>
<http>
<url>https://logs-prod-006.grafana.net/loki/api/v1/push</url>
<auth>
<username>813123</username>
<password>123</password>
</auth>
</http>
<format>
<label>
<pattern>name=${name},host=${HOSTNAME},level=%level,pid=${PID},thread=%thread</pattern>
<readMarkers>true</readMarkers>
</label>
<message>
<pattern>
{
"somefield":"1.0",
"playload": {
"pid":"${PID}",
"class":"%logger",
"thread":"%thread",
"traceId":"%X{traceId}",
"spanId":"%X{spanId}",
"message":"%message"
}
}
</pattern>
</message>
<sortByTime>true</sortByTime>
</format>
<verbose>false</verbose>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="LOKI"/>
</root>
</configuration>
This WORKS and I am able to see all logs in grafana loki.
By all logs, I mean my custom logs (from logger.info()) and the springboot logs
Now, in project THREE, I need to combine both, I need to send my logs to both kafka and loki.
Therefore, I added both log4j2.xml and the logback-spring.xml to my resources folder.
I combined all dependencies into one pom, and ran the program
I was expecting to see all logs in both locations.
However, the results are:
-
All logs (the logger.info() logs + the default springboot logs are in grafana loki
-
But only the default springboot logs are in the kafka topic, missing the custom logs.
By springboot log, I am referring to something like:
2024-12-13T04:35:32.364+08:00 INFO 4394 --- [ main] [ ] org.example.EventApplication : No active profile set, falling back to 1 default profile: "default"
Question, may I ask why the custom logs ended up in only grafana logs, but not Kafka?
What to do so I can see all logs in both places?
While your application can use multiple logging APIs (e.g. SLF4J, Log4j API, Apache Commons Logging (JCL), java.util.logging
(JUL)) at the same time, it can not use multiple logging implementations (e.g. Logback, Log4j Core).
If you have multiple logging implementations, each one will capture only the log events generated by a subset of logging APIs.
See Concepts in the Log4j Installation Guide for more details about logging APIs and logging implementations.
Since you can not use Log4j Core and Logback at the same time, you need to select a single logging implementation that has appenders for Apache Kafka and Grafana Loki.
I was not able to find a maintained Logback appender for Apache Kafka, but on the other hand there is a maintained Log4j Core appender for Grafana Loki.
So all you need to do is:
- Configure your application to use Log4j 2 Core as you did before (adding the
spring-boot-starter-log4j2
set of dependencies and removingspring-boot-starter-logging
). - Add the Kafka appender as you did previously.
- Add the
pl.tkowalcz.tjahzi:log4j2-appender
to your dependencies and configure a<Loki>
appender. See the documentation of the appender for more information.
Remarks:
-
You are using the unstructured
Pattern
layout in an attempt to generate JSON-like data. The output of your appenders will not always be valid JSON and this opens the door to log injection attacks. Use JSON Template Layout (additional dep needed) instead. -
We have deprecated the
Kafka
appender in Log4j Core 3, since it has virtually no users.
I have polled the Kafka community again, about the interest of having a Kafka appender in thisdev@kafka
thread with no answers so far.
If you need the Kafka appender for work, consider answering to that thread.