I am sending objects to a server (around 90 in sequence) when there is an Exception e.g. endpoint is down, Logback triggers a custom STMP appender, Which onlys sends 1 email with max. 10 logs and then there is a delay and the other logs get sent. Is it possible, that start()gets invoked too often?
Problem:
scheduler.scheduleAtFixedRate(this::sendEmail, 1, 1L, TimeUnit.HOURS);
Doesn't delay after executed once and directly executes another time so that many emails get sent in under an hour.
What do I have to change that the delay of the scheduler works?
After debugging I notices that
Attaching appender named [EMAIL] to Logger[ROOT]occured two times could that be the problem, if yes how can I fix that?
Image of the flood (As you can see, two email sent in under one hour...)
Logback.xml config:
<configuration debug="true">
<!-- Logging per console and per email -->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>
<!-- sets the format of the output -->
%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n
</Pattern>
</layout>
</appender>
<appender name="EMAIL" class="com.konverto.phonebillasaj.appenders.ScheduledSMTPAppender">
<subject>TESTING: %logger{20} - %m</subject>
<layout class="ch.qos.logback.classic.html.HTMLLayout" />
<smtpHost>smtp.xxx.net</smtpHost>
<smtpPort>587</smtpPort>
<STARTTLS>true</STARTTLS>
<username>[email protected]</username>
<password>myPass</password>
<to>[email protected]</to>
<from>[email protected]</from>
<maxMessages>10</maxMessages>
<!-- for testing , comment in production, default 256 -->
<cyclicBufferTracker class="ch.qos.logback.core.spi.CyclicBufferTracker">
<!-- Send just one log entry per email, ready for a lot of emails if you put one. -->
<bufferSize>1</bufferSize>
</cyclicBufferTracker>
<!-- for testing , comment in production, default asynchronousSending = true -->
<asynchronousSending>false</asynchronousSending>
</appender>
<logger name="com.konverto.phonebillasaj" level="error" additivity="false">
<appender-ref ref="EMAIL"/>
<appender-ref ref="CONSOLE" />
</logger>
<root level="error">
<appender-ref ref="EMAIL" />
<appender-ref ref="CONSOLE" />
</root>
Code of the appender:
public class ScheduledSMTPAppender extends SMTPAppender {
private final ThreadFactory tf = r -> {
Thread t = new Thread(r, "ScheduledSMTPAppender Thread");
t.setDaemon(true); //make daemon or it will prevent your program to exit
return t;
};
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, tf);
private final List<ILoggingEvent> events = new ArrayList<>();
private int maxMessages = 10;
public ScheduledSMTPAppender() {
super();
}
public ScheduledSMTPAppender(EventEvaluator<ILoggingEvent> eventEvaluator) {
super(eventEvaluator);
}
@Override public void start() {
super.start();
scheduler.scheduleAtFixedRate(this::sendEmail, 1, 1L, TimeUnit.HOURS);
}
@Override protected void sendBuffer(CyclicBuffer<ILoggingEvent> cb, ILoggingEvent lastEventObject) {
events.add(lastEventObject);
if (events.size() > maxMessages)
{
sendEmail();
}
}
//needs to be synchronized for thread safety
private synchronized void sendEmail() {
try {
if (events.isEmpty()){
return;
}
ILoggingEvent lastEvent = events.get(events.size() - 1);
events.remove(events.size() - 1);
CyclicBuffer<ILoggingEvent> cb;
if (events.isEmpty()) {
cb = new CyclicBuffer<>(1);
} else {
cb = new CyclicBuffer<>(events.size());
for (ILoggingEvent e : events){
cb.add(e);
}
}
super.sendBuffer(cb, lastEvent);
events.clear();
} catch (Exception e) {
//Important to have a catch all here or the scheduled task will die
addError("Error occurred while sending e-mail notification.", e);
}
}
//this allows to make "maxMessages" a parameter of your appender
public int getMaxMessages() {
return maxMessages;
}
public String getContentType() {
return layout.getContentType();
}
public void setMaxMessages(int maxMessages) {
this.maxMessages = maxMessages;
}
This seems to be the problem
This scheduler will queue up the sendEmail task for every appender initialised. Can you log the instance of the object or some id along with each log line, and each time start is called? That should clarify the scenario.
Just to be clear - I tried the same code using the Sysout instead of the logger (and initialised in the main method) and it worked as expected. Here's the code