Skip to content

Commit

Permalink
Use scheduler rather than Thread to handle the generation of error re…
Browse files Browse the repository at this point in the history
…port emails.
  • Loading branch information
stuartcaunt committed Nov 13, 2024
1 parent 40655b5 commit 9df938f
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 84 deletions.
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
package eu.ill.visa.business.notification.logging.filters;

import eu.ill.visa.business.ErrorReportEmailConfiguration;
import io.quarkus.logging.LoggingFilter;
import io.quarkus.mailer.Mailer;
import io.quarkus.mailer.MailerName;
import io.quarkus.runtime.Shutdown;
import io.quarkus.runtime.Startup;
import jakarta.enterprise.inject.spi.CDI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.logging.Filter;
import java.util.logging.Level;
import java.util.logging.LogRecord;
Expand All @@ -20,50 +15,18 @@ public class ErrorLogReportFilter implements Filter {

private static final Logger logger = LoggerFactory.getLogger(ErrorLogReportFilter.class);

private ErrorReporter errorReporter;
private Thread reporterThread;
private ErrorReporter errorReporter = null;

@Startup
public void start() {
try {
Mailer mailer = CDI.current().select(Mailer.class, MailerName.Literal.of("logging")).get();
ErrorReportEmailConfiguration configuration = CDI.current().select(ErrorReportEmailConfiguration.class).get();
List<String> addresses = configuration.to().orElse(null);
String toAddress = null;
List<String> ccAddresses = null;
if (addresses != null && !addresses.isEmpty()) {
toAddress = addresses.removeFirst();
ccAddresses = addresses;
}
String fromAddress = configuration.from().orElse(null);
String subject = configuration.subject().orElse(null);
int maxErrorsPerReport = configuration.maxErrors();
this.errorReporter = CDI.current().select(ErrorReporter.class).get();

boolean enabled = (toAddress != null && fromAddress != null && subject != null && configuration.enabled());

if (enabled) {
logger.info("Starting Error Reporter");
this.errorReporter = new ErrorReporter(mailer, subject, toAddress, ccAddresses, fromAddress, maxErrorsPerReport);
this.reporterThread = new Thread(this.errorReporter);
this.reporterThread.start();
}
} catch (Exception e) {
logger.warn("Failed to start Error Reporter: {}", e.getMessage());
logger.warn("Failed to get Error Reporter: {}", e.getMessage());
}
}

@Shutdown
public void stop() {
if (this.errorReporter != null) {
this.errorReporter.stop();
this.errorReporter = null;
try {
logger.info("Stopping Error Reporter");
this.reporterThread.join();
} catch (InterruptedException ignored) {
}
}
}

@Override
public boolean isLoggable(LogRecord record) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package eu.ill.visa.business.notification.logging.filters;

import eu.ill.visa.business.ErrorReportEmailConfiguration;
import io.quarkus.mailer.Mail;
import io.quarkus.mailer.Mailer;
import io.quarkus.mailer.MailerName;
import io.smallrye.mutiny.Uni;
import io.smallrye.mutiny.infrastructure.Infrastructure;
import jakarta.enterprise.context.ApplicationScoped;
import org.jboss.logmanager.ExtLogRecord;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -12,13 +15,16 @@
import java.io.StringWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.logging.LogRecord;
import java.util.stream.Collectors;

import static java.lang.String.format;

public class ErrorReporter implements Runnable {
@ApplicationScoped
public class ErrorReporter {

public final static DateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private static final Logger logger = LoggerFactory.getLogger(ErrorReporter.class);
Expand All @@ -30,67 +36,65 @@ public class ErrorReporter implements Runnable {
private final String fromAddress;
private final int maxErrorsPerReport;

private final boolean enabled;

private boolean running = false;
private Date lastReportingTime = null;
private List<ErrorEvent> events = new ArrayList<>();


public ErrorReporter(final Mailer mailer,
final String subject,
final String toAddress,
final List<String> ccAddresses,
final String fromAddress,
final int maxErrorsPerReport) {
public ErrorReporter(final @MailerName("logging") Mailer mailer,
final ErrorReportEmailConfiguration configuration) {
this.mailer = mailer;
this.subject = subject;
this.toAddress = toAddress;
this.ccAddresses = ccAddresses;
this.fromAddress = fromAddress;
this.maxErrorsPerReport = maxErrorsPerReport;
}

@Override
public void run() {
this.running = true;
List<String> addresses = configuration.to().orElse(null);
if (addresses != null && !addresses.isEmpty()) {
this.toAddress = addresses.removeFirst();
this.ccAddresses = addresses;

while (running) {
try {
Thread.sleep(1000);
this.work();
} catch (InterruptedException ignored) {
}
} else {
this.toAddress = null;
this.ccAddresses = new ArrayList<>();
}

}
this.fromAddress = configuration.from().orElse(null);
this.subject = configuration.subject().orElse(null);
this.maxErrorsPerReport = configuration.maxErrors();

public void stop() {
this.running = false;
this.enabled = (toAddress != null && fromAddress != null && subject != null && configuration.enabled());
}

public synchronized void work() {
Date currentTime = new Date();
Long elapsedTime = this.lastReportingTime == null ? null : currentTime.getTime() - this.lastReportingTime.getTime();
if ((!this.events.isEmpty() && (elapsedTime == null || elapsedTime >= 60000)) || this.events.size() >= this.maxErrorsPerReport) {

final List<ErrorEvent> events = this.events;
this.events = new ArrayList<>();

Uni.createFrom()
.item(events)
.emitOn(Infrastructure.getDefaultWorkerPool())
.subscribe()
.with(this::generateReport, Throwable::printStackTrace);
public synchronized void handleMaxErrors() {
if (this.events.size() >= this.maxErrorsPerReport) {
this.generateReportInVirtualThread(events);
}
}

this.lastReportingTime = currentTime;
public synchronized void handleCurrentErrors() {
if (!this.events.isEmpty()) {
this.generateReportInVirtualThread(events);
}
}

public synchronized void onRecord(LogRecord record) {
this.events.add(new ErrorEvent(new Date(), record, Thread.currentThread().getStackTrace()));
if (this.enabled) {
this.events.add(new ErrorEvent(new Date(), record, Thread.currentThread().getStackTrace()));
}
}

private void generateReportInVirtualThread(final List<ErrorEvent> events) {
this.events = new ArrayList<>();

Uni.createFrom()
.item(events)
.emitOn(Infrastructure.getDefaultWorkerPool())
.subscribe()
.with(this::generateReport, Throwable::printStackTrace);
}

private void generateReport(final List<ErrorEvent> events) {
if (!this.enabled) {
return;
}

logger.info("Generating error report for {} events ", events.size());

String errors = events.stream()
Expand Down
3 changes: 2 additions & 1 deletion visa-core/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ quarkus:
host: ${VISA_NOTIFICATION_EMAIL_ADAPTER_HOST}
port: ${VISA_NOTIFICATION_EMAIL_ADAPTER_PORT}
logging:
mock: ${VISA_LOGGING_EMAIL_APPENDER_MOCKED:true}
host: ${VISA_LOGGING_EMAIL_APPENDER_HOST}
port: ${VISA_LOGGING_EMAIL_APPENDER_PORT:25}
ssl: ${VISA_LOGGING_EMAIL_APPENDER_SSL:false}
Expand Down Expand Up @@ -111,7 +112,7 @@ business:
- ${VISA_LOGGING_EMAIL_APPENDER_RECIPIENT_ADDRESS}
from: ${VISA_LOGGING_EMAIL_APPENDER_FROM_ADDRESS}
subject: ${VISA_LOGGING_EMAIL_SUBJECT}
maxErrors: ${VISA_LOGGING_EMAIL_MAX_ERRORS:20}
maxErrors: ${VISA_LOGGING_EMAIL_MAX_ERRORS:50}
signature:
privateKeyPath: ${VISA_VDI_SIGNATURE_PRIVATE_KEY_PATH}
publicKeyPath: ${VISA_VDI_SIGNATURE_PUBLIC_KEY_PATH}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package eu.ill.visa.scheduler.jobs;

import eu.ill.visa.business.notification.logging.filters.ErrorReporter;
import io.quarkus.scheduler.Scheduled;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@ApplicationScoped
public class ErrorReporterJob {

private static final Logger logger = LoggerFactory.getLogger(ErrorReporterJob.class);

private final ErrorReporter errorReporter;

@Inject
public ErrorReporterJob(final ErrorReporter errorReporter) {
this.errorReporter = errorReporter;
}

@Scheduled(cron="5/10 * * ? * *", concurrentExecution = Scheduled.ConcurrentExecution.SKIP)
public void maxErrorsScheduler() {
this.errorReporter.handleMaxErrors();
}

@Scheduled(cron="0 * * ? * *", concurrentExecution = Scheduled.ConcurrentExecution.SKIP)
public void minuteScheduler() {
this.errorReporter.handleCurrentErrors();
}
}

0 comments on commit 9df938f

Please sign in to comment.