From 9df938f3f557542d563cc5b0ff29d6fa75437578 Mon Sep 17 00:00:00 2001 From: Stuart Caunt Date: Wed, 13 Nov 2024 15:26:17 +0100 Subject: [PATCH] Use scheduler rather than Thread to handle the generation of error report emails. --- .../logging/filters/ErrorLogReportFilter.java | 43 +-------- .../logging/filters/ErrorReporter.java | 90 ++++++++++--------- visa-core/src/main/resources/application.yml | 3 +- .../visa/scheduler/jobs/ErrorReporterJob.java | 31 +++++++ 4 files changed, 83 insertions(+), 84 deletions(-) create mode 100644 visa-scheduler/src/main/java/eu/ill/visa/scheduler/jobs/ErrorReporterJob.java diff --git a/visa-business/src/main/java/eu/ill/visa/business/notification/logging/filters/ErrorLogReportFilter.java b/visa-business/src/main/java/eu/ill/visa/business/notification/logging/filters/ErrorLogReportFilter.java index 7c981ddb..5425cd67 100644 --- a/visa-business/src/main/java/eu/ill/visa/business/notification/logging/filters/ErrorLogReportFilter.java +++ b/visa-business/src/main/java/eu/ill/visa/business/notification/logging/filters/ErrorLogReportFilter.java @@ -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; @@ -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 addresses = configuration.to().orElse(null); - String toAddress = null; - List 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) { diff --git a/visa-business/src/main/java/eu/ill/visa/business/notification/logging/filters/ErrorReporter.java b/visa-business/src/main/java/eu/ill/visa/business/notification/logging/filters/ErrorReporter.java index f7e4a3e7..c943c144 100644 --- a/visa-business/src/main/java/eu/ill/visa/business/notification/logging/filters/ErrorReporter.java +++ b/visa-business/src/main/java/eu/ill/visa/business/notification/logging/filters/ErrorReporter.java @@ -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; @@ -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); @@ -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 events = new ArrayList<>(); - public ErrorReporter(final Mailer mailer, - final String subject, - final String toAddress, - final List 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 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 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 events) { + this.events = new ArrayList<>(); + + Uni.createFrom() + .item(events) + .emitOn(Infrastructure.getDefaultWorkerPool()) + .subscribe() + .with(this::generateReport, Throwable::printStackTrace); } private void generateReport(final List events) { + if (!this.enabled) { + return; + } + logger.info("Generating error report for {} events ", events.size()); String errors = events.stream() diff --git a/visa-core/src/main/resources/application.yml b/visa-core/src/main/resources/application.yml index 8db3d3e1..33f9b427 100644 --- a/visa-core/src/main/resources/application.yml +++ b/visa-core/src/main/resources/application.yml @@ -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} @@ -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} diff --git a/visa-scheduler/src/main/java/eu/ill/visa/scheduler/jobs/ErrorReporterJob.java b/visa-scheduler/src/main/java/eu/ill/visa/scheduler/jobs/ErrorReporterJob.java new file mode 100644 index 00000000..18b31234 --- /dev/null +++ b/visa-scheduler/src/main/java/eu/ill/visa/scheduler/jobs/ErrorReporterJob.java @@ -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(); + } +}