Skip to content

Commit

Permalink
feat: 🎸 Add concatenated logs to elastic (#932)
Browse files Browse the repository at this point in the history
  • Loading branch information
mattboll authored Jan 16, 2025
1 parent c57bd21 commit 210da92
Show file tree
Hide file tree
Showing 6 changed files with 182 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package fr.dossierfacile.api.front.log;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.classic.spi.IThrowableProxy;
import ch.qos.logback.classic.spi.StackTraceElementProxy;
import ch.qos.logback.core.AppenderBase;
import ch.qos.logback.core.Context;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class CustomAppender extends AppenderBase<ILoggingEvent> {
private static final Map<String, List<LogModel>> logsByRequestId = new ConcurrentHashMap<>();

@Override
protected void append(ILoggingEvent eventObject) {
String requestId = MDC.get("request_id");
if (requestId != null) {
String message = eventObject.getFormattedMessage();

if (eventObject.getLevel().isGreaterOrEqual(Level.ERROR)) {
IThrowableProxy throwableProxy = eventObject.getThrowableProxy();
if (throwableProxy != null) {
StringBuilder stackTrace = new StringBuilder();
for (StackTraceElementProxy line : throwableProxy.getStackTraceElementProxyArray()) {
stackTrace.append(line).append("\n");
}
message += "\n" + stackTrace;
}
}

LogModel log = new LogModel(message, eventObject.getLevel());
logsByRequestId.computeIfAbsent(requestId, id -> new ArrayList<>())
.add(log);
}
}

public static List<LogModel> getLogsForRequest(String requestId) {
return logsByRequestId.getOrDefault(requestId, List.of());
}

public static void clearLogsForRequest(String requestId) {
logsByRequestId.remove(requestId);
}

public static void attachToRootLogger() {
Logger rootLogger = (Logger) LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
CustomAppender customAppender = new CustomAppender();
customAppender.setName("CUSTOM");
Context context = rootLogger.getLoggerContext();
customAppender.setContext(context);
customAppender.start();
rootLogger.addAppender(customAppender);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package fr.dossierfacile.api.front.log;

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception e) {
log.error("Unhandled exception: ", e);
return new ResponseEntity<>("Internal Server Error", HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package fr.dossierfacile.api.front.log;

import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.LoggingEvent;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import net.logstash.logback.appender.LogstashTcpSocketAppender;
import org.jetbrains.annotations.NotNull;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.PostConstruct;
import java.io.IOException;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;

@Component
public class LogAggregationFilter extends OncePerRequestFilter {
private LogstashTcpSocketAppender logstashAppender;

@PostConstruct
public void init() {
Logger rootLogger = (Logger) LoggerFactory.getLogger(org.slf4j.Logger.ROOT_LOGGER_NAME);
logstashAppender = (LogstashTcpSocketAppender) rootLogger.getAppender("LOGSTASH");
if (logstashAppender == null) {
throw new IllegalStateException("Logstash appender (LOGSTASH) not found in Logback configuration.");
}
}

@Override
protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String requestId = MDC.get("request_id");
try {
filterChain.doFilter(request, response);
} finally {
List<LogModel> logs = CustomAppender.getLogsForRequest(requestId);
String logMessage = logs.stream().map(LogModel::getMessage).collect(Collectors.joining());

String enrichedLogs = String.format(
"Request completed: URI:%s, Method:%s, Status:%d, Logs:%s",
request.getRequestURI(),
request.getMethod(),
response.getStatus(),
logMessage
);

Level logLevel = logs.stream().map(LogModel::getLevel).max(Comparator.comparingInt(Level::toInt)).orElse(Level.INFO);

LoggingEvent enrichedEvent = new LoggingEvent();
enrichedEvent.setTimeStamp(System.currentTimeMillis());
enrichedEvent.setLoggerName("AggregatedHttpLogger");
enrichedEvent.setLevel(logLevel);
enrichedEvent.setThreadName(Thread.currentThread().getName());
enrichedEvent.setMessage(enrichedLogs);

LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
enrichedEvent.setLoggerContext(loggerContext);

logstashAppender.doAppend(enrichedEvent);

CustomAppender.clearLogsForRequest(requestId);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package fr.dossierfacile.api.front.log;

import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class LogConfiguration {

@PostConstruct
public void setupCustomLogging() {
CustomAppender.attachToRootLogger();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package fr.dossierfacile.api.front.log;

import ch.qos.logback.classic.Level;
import lombok.Getter;

@Getter
public class LogModel {
String message;
Level level;

public LogModel(String message, Level level) {
this.message = message;
this.level = level;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,11 @@
</encoder>
</appender>

<appender name="CUSTOM" class="fr.dossierfacile.api.front.log.CustomAppender"/>

<root level="${LOG_LEVEL}">
<appender-ref ref="LOGSTASH"/>
<appender-ref ref="CUSTOM"/>
<appender-ref ref="STDOUT"/>
<springProfile name="dev">
<appender-ref ref="FILE"/>
Expand Down

0 comments on commit 210da92

Please sign in to comment.